├── .dockerignore ├── .gitignore ├── .proxyrc.json ├── Cargo.lock ├── Cargo.toml ├── Cross.toml ├── Dockerfile ├── LICENSE ├── README.md ├── VERSION ├── admin ├── .gitignore ├── package.json ├── rome.json ├── src │ ├── app │ │ ├── router.tsx │ │ └── store.ts │ ├── favicon.ico │ ├── index.html │ ├── main.tsx │ ├── style.css │ └── types.d.ts └── tsconfig.json ├── docker-compose.yml ├── example ├── .env ├── .gitignore ├── .proxyrc.json ├── README.md ├── package.json ├── server │ ├── key.pub │ └── node_passport │ │ ├── index.js │ │ ├── package-lock.json │ │ └── package.json ├── src │ ├── App.css │ ├── App.tsx │ ├── Auth.tsx │ ├── Bootstrap.tsx │ ├── index.css │ ├── index.html │ ├── index.tsx │ ├── logo.svg │ └── react-app-env.d.ts └── tsconfig.json ├── guides ├── .gitignore ├── README.md ├── babel.config.js ├── docs │ ├── 01-quickstart.md │ ├── 02-install │ │ └── _category.json │ ├── 03-develop │ │ ├── 01-private-public.md │ │ ├── 02-auth-shell.md │ │ ├── 03-protect-your-api-endpoints.md │ │ ├── 04-passwordless-verify-reset.md │ │ ├── 05-google-auth.md │ │ ├── 06-update-email.md │ │ ├── 07-api-keys.md │ │ └── _category_.json │ └── 04-server │ │ ├── _category_.json │ │ └── config.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src │ └── css │ │ └── custom.css ├── static │ ├── .nojekyll │ ├── img │ │ ├── docusaurus.png │ │ ├── favicon.ico │ │ ├── logo.svg │ │ ├── undraw_docusaurus_mountain.svg │ │ ├── undraw_docusaurus_react.svg │ │ └── undraw_docusaurus_tree.svg │ └── js │ │ └── plausible.js └── tsconfig.json ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── admin │ ├── .gitignore │ ├── .swcrc │ ├── package.json │ ├── rome.json │ ├── scripts │ │ └── clean.mjs │ ├── src │ │ ├── component │ │ │ ├── app_shell.tsx │ │ │ ├── button.ts │ │ │ ├── create_project.tsx │ │ │ ├── date.tsx │ │ │ ├── nav.tsx │ │ │ ├── page.tsx │ │ │ ├── project_redirect.tsx │ │ │ ├── search.tsx │ │ │ ├── sidebar.tsx │ │ │ └── text.tsx │ │ ├── data │ │ │ ├── admin_api.ts │ │ │ └── project.ts │ │ ├── pages │ │ │ ├── auth │ │ │ │ ├── auth.tsx │ │ │ │ └── index.ts │ │ │ ├── methods │ │ │ │ ├── index.tsx │ │ │ │ └── methods.tsx │ │ │ ├── settings │ │ │ │ ├── component │ │ │ │ │ ├── delete.tsx │ │ │ │ │ ├── email.tsx │ │ │ │ │ ├── layout.tsx │ │ │ │ │ ├── project.tsx │ │ │ │ │ └── public_keys.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── settings.tsx │ │ │ ├── user │ │ │ │ ├── component │ │ │ │ │ ├── layout.tsx │ │ │ │ │ └── user_form.tsx │ │ │ │ ├── create.tsx │ │ │ │ ├── empty.tsx │ │ │ │ ├── index.ts │ │ │ │ └── user.tsx │ │ │ └── users │ │ │ │ ├── index.tsx │ │ │ │ └── users.tsx │ │ ├── types.d.ts │ │ └── utils │ │ │ ├── auth.ts │ │ │ ├── context.ts │ │ │ └── index.ts │ └── tsconfig.json ├── email-templates │ ├── .env.sample │ ├── .gitignore │ ├── .npmignore │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── component │ │ │ ├── button.tsx │ │ │ └── index.tsx │ │ ├── config.ts │ │ └── template │ │ │ ├── change_email │ │ │ ├── index.ts │ │ │ ├── preview.ts │ │ │ ├── template.tsx │ │ │ ├── translations.ts │ │ │ └── types.ts │ │ │ ├── confirm_email_change │ │ │ ├── index.ts │ │ │ ├── preview.ts │ │ │ ├── template.tsx │ │ │ ├── translations.ts │ │ │ └── types.ts │ │ │ ├── password_changed │ │ │ ├── index.ts │ │ │ ├── preview.ts │ │ │ ├── template.tsx │ │ │ ├── translations.ts │ │ │ └── types.ts │ │ │ ├── password_reset │ │ │ ├── index.ts │ │ │ ├── preview.ts │ │ │ ├── template.tsx │ │ │ ├── translations.ts │ │ │ └── types.ts │ │ │ ├── passwordless │ │ │ ├── index.ts │ │ │ ├── preview.ts │ │ │ ├── template.tsx │ │ │ ├── translations.ts │ │ │ └── types.ts │ │ │ └── verify_email │ │ │ ├── index.ts │ │ │ ├── preview.ts │ │ │ ├── template.tsx │ │ │ ├── translations.ts │ │ │ └── types.ts │ └── tsconfig.json ├── rust │ ├── client-clap │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── examples │ │ │ ├── main.rs │ │ │ └── signin.rs │ │ └── src │ │ │ └── lib.rs │ ├── client │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── examples │ │ │ ├── api_key.rs │ │ │ └── refresh.rs │ │ └── src │ │ │ ├── error.rs │ │ │ ├── keys.rs │ │ │ ├── lib.rs │ │ │ └── test.rs │ ├── sdk-rocket │ │ ├── Cargo.toml │ │ ├── example │ │ │ ├── Cargo.lock │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── main.rs │ │ └── src │ │ │ └── lib.rs │ ├── sdk │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── lib.rs │ │ │ └── test.rs │ └── types │ │ ├── Cargo.toml │ │ └── src │ │ ├── api_key.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ └── session.rs ├── test-helper │ ├── .npmignore │ ├── package.json │ ├── src │ │ └── main.ts │ └── tsconfig.json └── web │ ├── sdk-admin │ ├── .gitignore │ ├── .npmignore │ ├── package.json │ ├── src │ │ └── main.ts │ └── tsconfig.json │ ├── sdk-react │ ├── .gitignore │ ├── .npmignore │ ├── package.json │ ├── src │ │ └── main.ts │ └── tsconfig.json │ ├── sdk │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── @types │ │ │ └── shallow-equal.d.ts │ │ ├── client.ts │ │ ├── error.ts │ │ ├── interceptor.ts │ │ ├── keys.ts │ │ ├── main.ts │ │ ├── mock_client.ts │ │ ├── session.ts │ │ ├── storage.ts │ │ ├── tokens.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── test │ │ ├── mock │ │ │ └── storage.ts │ │ └── session.test.ts │ └── tsconfig.json │ └── ui │ ├── .npmignore │ ├── .storybook │ ├── main.js │ ├── preview-head.html │ └── preview.js │ ├── .swcrc │ ├── README.md │ ├── jest-setup.js │ ├── jest.config.js │ ├── package.json │ ├── src │ ├── auth │ │ ├── auth.stories.tsx │ │ └── index.tsx │ ├── auth_shell.tsx │ ├── component │ │ ├── arrow.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── check.tsx │ │ ├── disclaimer.tsx │ │ ├── google_auth_icon.tsx │ │ ├── header.tsx │ │ ├── input.tsx │ │ ├── loading.tsx │ │ └── splashscreen.tsx │ ├── context │ │ ├── config.tsx │ │ └── translation.tsx │ ├── main.ts │ ├── oauth │ │ └── index.tsx │ ├── overview │ │ ├── index.tsx │ │ └── overview.stories.tsx │ ├── password │ │ ├── index.tsx │ │ └── password.stories.tsx │ ├── password_reset │ │ ├── check.tsx │ │ ├── index.tsx │ │ ├── password_reset.stories.tsx │ │ └── set_password.tsx │ ├── passwordless │ │ ├── check.tsx │ │ ├── confirm.tsx │ │ ├── enter_email.tsx │ │ ├── index.tsx │ │ ├── passwordless.stories.tsx │ │ └── passwordless.test.tsx │ ├── test-utils.tsx │ ├── user │ │ ├── set_password.tsx │ │ ├── update_email │ │ │ ├── confirm.tsx │ │ │ ├── reject.tsx │ │ │ ├── update_email.stories.tsx │ │ │ └── update_email.test.tsx │ │ └── user.stories.tsx │ ├── utils.ts │ └── verify_email │ │ ├── index.tsx │ │ └── verify_email.stories.tsx │ ├── styles.css │ └── tsconfig.json ├── scripts ├── build │ ├── admin.ps1 │ ├── docker.ps1 │ ├── docs-web.ps1 │ ├── guides.ps1 │ ├── linux-musl.ps1 │ ├── templates.ps1 │ ├── version.ps1 │ └── website.ps1 ├── mock-auth-server │ ├── package.json │ ├── src │ │ └── main.ts │ └── tsconfig.json ├── mock-key-server │ ├── package.json │ ├── src │ │ └── main.ts │ └── tsconfig.json ├── seeds │ ├── data │ │ ├── projects.ts │ │ ├── settings.ts │ │ ├── template.ts │ │ └── users.ts │ ├── dev │ │ └── init.ts │ ├── keys │ │ ├── admin.encrypted │ │ ├── admin.private │ │ ├── admin.public │ │ ├── project.encrypted │ │ ├── project.private │ │ └── project.public │ ├── knexfile.ts │ ├── package.json │ └── tsconfig.json └── utils.ps1 ├── server ├── .env ├── .gitignore ├── Cargo.toml ├── Vulpo.toml ├── docker-compose.yml ├── migrations │ ├── 00000000000000_initial_setup.down.sql │ ├── 00000000000000_initial_setup.up.sql │ ├── 20201128210000_create_project.down.sql │ ├── 20201128210000_create_project.up.sql │ ├── 20201128214822_create_users.down.sql │ ├── 20201128214822_create_users.up.sql │ ├── 20201128215246_create_session.down.sql │ ├── 20201128215246_create_session.up.sql │ ├── 20201128223759_create_passwordless.down.sql │ ├── 20201128223759_create_passwordless.up.sql │ ├── 20201203190902_create_keys.down.sql │ ├── 20201203190902_create_keys.up.sql │ ├── 20201217192635_password_reset.down.sql │ ├── 20201217192635_password_reset.up.sql │ ├── 20210124203105_create_passwords.down.sql │ ├── 20210124203105_create_passwords.up.sql │ ├── 20210125093928_create_settings.down.sql │ ├── 20210125093928_create_settings.up.sql │ ├── 20210212210113_create_template.down.sql │ ├── 20210212210113_create_template.up.sql │ ├── 20210225201503_create_verify_email.down.sql │ ├── 20210225201503_create_verify_email.up.sql │ ├── 20210305204923_create_refresh_access_tokens.down.sql │ ├── 20210305204923_create_refresh_access_tokens.up.sql │ ├── 20210918195903_create_email_change_request.down.sql │ ├── 20210918195903_create_email_change_request.up.sql │ ├── 20211002203303_create_api_keys.down.sql │ ├── 20211002203303_create_api_keys.up.sql │ ├── 20220502141736_create_oauth.down.sql │ └── 20220502141736_create_oauth.up.sql ├── rustfmt.toml ├── sqlx-data.json └── src │ ├── admin │ ├── create.rs │ ├── create_user.rs │ ├── data │ │ ├── admin.rs │ │ └── mod.rs │ ├── has_admin.rs │ ├── mod.rs │ ├── project.rs │ └── sql │ │ ├── create_admin.sql │ │ ├── create_project.sql │ │ ├── create_user.sql │ │ ├── get_project.sql │ │ ├── has_admin.sql │ │ ├── project_list.sql │ │ └── set_admin.sql │ ├── api_key │ ├── data.rs │ ├── delete.rs │ ├── generate.rs │ ├── list.rs │ ├── mod.rs │ ├── sql │ │ ├── create_api_key.sql │ │ ├── delete_api_key.sql │ │ ├── get_claims.sql │ │ ├── get_token.sql │ │ └── list_api_keys.sql │ └── verify.rs │ ├── cli.rs │ ├── config │ └── mod.rs │ ├── cors.rs │ ├── crypto.rs │ ├── file.rs │ ├── init.rs │ ├── keys │ ├── data │ │ └── mod.rs │ ├── keys.rs │ ├── mod.rs │ ├── public_keys.rs │ └── sql │ │ ├── get_by_project.sql │ │ ├── get_private_key.sql │ │ ├── get_public_key.sql │ │ └── get_public_keys.sql │ ├── mail │ ├── data │ │ ├── mod.rs │ │ └── verify_email.rs │ ├── mod.rs │ └── sql │ │ ├── get_verify_email.sql │ │ ├── insert_verify_email.sql │ │ ├── unverify_email.sql │ │ └── verify_email.sql │ ├── main.rs │ ├── migration.rs │ ├── oauth │ ├── data │ │ ├── google.rs │ │ └── mod.rs │ ├── google.rs │ ├── mod.rs │ └── sql │ │ ├── get_config.sql │ │ ├── get_oauth_request_state.sql │ │ ├── get_user_id.sql │ │ ├── insert_config.sql │ │ ├── insert_oauth_request_state.sql │ │ └── upsert_oauth_data.sql │ ├── password │ ├── data │ │ ├── mod.rs │ │ ├── password.rs │ │ └── password_reset.rs │ ├── mod.rs │ ├── reset.rs │ ├── signin.rs │ ├── signup.rs │ └── sql │ │ ├── create_password.sql │ │ ├── get_by_email.sql │ │ ├── get_password_change_request.sql │ │ ├── insert_password_change_request.sql │ │ ├── remove_password_change_request.sql │ │ └── set_password.sql │ ├── passwordless │ ├── confirm.rs │ ├── data │ │ ├── mod.rs │ │ └── passwordless.rs │ ├── mod.rs │ ├── request_passwordless.rs │ ├── sql │ │ ├── confirm_passwordless.sql │ │ ├── get_passwordless.sql │ │ ├── insert_passwordless_token.sql │ │ └── remove_all.sql │ └── verify.rs │ ├── project │ ├── data │ │ ├── flags.rs │ │ ├── mod.rs │ │ └── project.rs │ ├── delete.rs │ ├── flags.rs │ ├── mod.rs │ ├── set_flags.rs │ └── sql │ │ ├── delete_project.sql │ │ ├── get_flags.sql │ │ ├── get_password_alg.sql │ │ ├── get_password_alg_by_user.sql │ │ ├── get_project_domain.sql │ │ ├── is_admin.sql │ │ ├── set_flags.sql │ │ └── set_project_settings.sql │ ├── server.rs │ ├── session │ ├── data │ │ ├── access_token.rs │ │ ├── mod.rs │ │ └── session.rs │ ├── mod.rs │ ├── refresh.rs │ └── sql │ │ ├── confirm_session.sql │ │ ├── create_session.sql │ │ ├── extend_session.sql │ │ ├── get_session.sql │ │ ├── remove_all_sessions.sql │ │ ├── remove_session.sql │ │ ├── remove_user_sessions.sql │ │ └── token_is_valid.sql │ ├── settings │ ├── data.rs │ ├── email.rs │ ├── mod.rs │ ├── project.rs │ └── sql │ │ ├── get_email_settings.sql │ │ ├── get_template_settings.sql │ │ └── insert_email_settings.sql │ ├── template │ ├── config.rs │ ├── data │ │ ├── mod.rs │ │ └── translations.rs │ ├── mod.rs │ ├── sql │ │ ├── get_template.sql │ │ ├── get_translation_by_lang_code.sql │ │ ├── get_translations_by_project.sql │ │ ├── get_user_translation.sql │ │ ├── remove_translation.sql │ │ ├── set_template.sql │ │ └── set_translation.sql │ ├── template.rs │ └── translations.rs │ └── user │ ├── change_email.rs │ ├── data │ ├── email.rs │ ├── mod.rs │ └── user.rs │ ├── delete_account.rs │ ├── disable.rs │ ├── get.rs │ ├── list.rs │ ├── mod.rs │ ├── set_password.rs │ ├── sign_out.rs │ ├── sql │ ├── create_passwordless.sql │ ├── create_provider.sql │ ├── create_user.sql │ ├── disable_user.sql │ ├── email │ │ ├── get_confirm_token.sql │ │ ├── get_reset_token.sql │ │ ├── insert_change_request.sql │ │ ├── reset_email.sql │ │ └── set_email.sql │ ├── enable_user.sql │ ├── get_by_email.sql │ ├── get_by_id.sql │ ├── get_project_id.sql │ ├── get_user_count.sql │ ├── get_users.sql │ ├── remove_user.sql │ └── set_user.sql │ ├── update.rs │ └── verify_email.rs ├── tests ├── admin_e2e │ ├── .gitignore │ ├── package.json │ ├── playwright.config.ts │ ├── scripts │ │ └── setup.mjs │ └── tests │ │ ├── dashboard.spec.ts │ │ └── user │ │ └── detail.spec.ts ├── api_tests │ ├── jest.config.js │ ├── package.json │ ├── scripts │ │ └── global_teardown.ts │ ├── src │ │ ├── api_key │ │ │ ├── admin_guard.test.ts │ │ │ ├── delete_api_key.test.ts │ │ │ ├── generate.test.ts │ │ │ ├── list_api_keys.test.ts │ │ │ ├── utils.ts │ │ │ └── verify.test.ts │ │ ├── email_password │ │ │ ├── reset.test.ts │ │ │ ├── signin.test.ts │ │ │ └── signup.test.ts │ │ ├── passwordless │ │ │ ├── confirm.test.ts │ │ │ ├── request_passwordless.test.ts │ │ │ └── verify.test.ts │ │ ├── project │ │ │ └── delete.test.ts │ │ ├── session │ │ │ └── refresh.test.ts │ │ ├── template │ │ │ └── translations.test.ts │ │ ├── user │ │ │ ├── change_email.test.ts │ │ │ ├── create.test.ts │ │ │ ├── delete.test.ts │ │ │ ├── get.test.ts │ │ │ ├── set_password.test.ts │ │ │ ├── sign_out.test.ts │ │ │ ├── update.test.ts │ │ │ └── verify_email.test.ts │ │ └── utils │ │ │ ├── admin.ts │ │ │ ├── ajv.ts │ │ │ ├── auth.ts │ │ │ ├── crypto.ts │ │ │ ├── db.ts │ │ │ ├── env.ts │ │ │ ├── http.ts │ │ │ ├── passwordless.ts │ │ │ ├── schema │ │ │ ├── api-key.ts │ │ │ ├── api-keys.ts │ │ │ ├── claims.ts │ │ │ ├── error.ts │ │ │ ├── generate-api-key.ts │ │ │ ├── passwordless-response.ts │ │ │ ├── session-response.ts │ │ │ ├── translations.ts │ │ │ └── user.ts │ │ │ └── user.ts │ └── tsconfig.json ├── server_sdk │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── api_key.test.ts │ │ ├── jwt.test.ts │ │ └── types.ts │ └── tsconfig.json └── ui_e2e │ ├── package.json │ ├── playwright.config.ts │ ├── scripts │ └── global_teardown.ts │ └── tests │ ├── api_keys.spec.ts │ ├── flags.spec.ts │ ├── overview.spec.ts │ ├── password │ ├── reset.spec.ts │ ├── signin.spec.ts │ └── signup.spec.ts │ ├── passwordless.spec.ts │ ├── public.spec.ts │ ├── update_email.spec.ts │ └── utils │ ├── db.ts │ ├── index.ts │ ├── project.ts │ ├── signin.ts │ └── user.ts ├── tsconfig.json └── website ├── .eslintrc.json ├── .gitignore ├── README.md ├── components ├── auth_example.tsx ├── auth_memory.tsx ├── auth_page.tsx ├── banner.tsx ├── header_nav.tsx └── mock_browser.tsx ├── hosting.toml ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── index.tsx └── ui.tsx ├── public ├── favicon.ico └── fonts │ ├── prompt-blackitalic-webfont.woff │ ├── prompt-blackitalic-webfont.woff2 │ ├── prompt-extrabold-webfont.woff │ ├── prompt-extrabold-webfont.woff2 │ ├── prompt-regular-webfont.woff │ └── prompt-regular-webfont.woff2 ├── styles ├── Home.module.css └── globals.css └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules/ 2 | **/packages/**/lib/ 3 | **/packages/**/browser/ 4 | **.log 5 | **.env 6 | target 7 | .parcel-cache -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules/ 2 | **/packages/**/lib/ 3 | **/packages/**/browser/ 4 | **.log 5 | **.env 6 | target 7 | out 8 | **/.parcel-cache -------------------------------------------------------------------------------- /.proxyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://127.0.0.1:8000/" 4 | } 5 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "server", 4 | "packages/rust/sdk", 5 | "packages/rust/sdk-rocket", 6 | "packages/rust/types", 7 | "packages/rust/client", 8 | "packages/rust/client-clap", 9 | ] 10 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [build.env] 2 | passthrough = ["SQLX_OFFLINE"] 3 | 4 | [target.x86_64-unknown-linux-musl] 5 | image = "clux/muslrust:1.68.2" 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Assets 2 | FROM node:16.10.0 as build_assets 3 | 4 | ARG version 5 | ENV VulpoAuthVersion=$version 6 | 7 | WORKDIR /usr/src 8 | RUN npm set unsafe-perm true 9 | 10 | COPY . ./ 11 | RUN npm install 12 | RUN npx lerna bootstrap 13 | 14 | WORKDIR /usr/src/admin 15 | RUN npm run build 16 | 17 | WORKDIR /usr/src/packages/email-templates 18 | RUN npm run build 19 | 20 | 21 | # SERVER 22 | FROM clux/muslrust:1.68.2 AS build 23 | 24 | ARG version 25 | ENV VulpoAuthVersion=$version 26 | 27 | WORKDIR /usr/src 28 | 29 | COPY . ./ 30 | COPY --from=build_assets /usr/src/admin/dist /usr/src/admin/dist 31 | COPY --from=build_assets /usr/src/packages/email-templates/build /usr/src/packages/email-templates/build 32 | 33 | RUN cargo build -p vulpo_server --release 34 | 35 | # Copy the statically-linked binary into a scratch container. 36 | FROM scratch 37 | COPY --from=build /usr/src/target/x86_64-unknown-linux-musl/release/vulpo_server /vulpo_server 38 | ENV VULPO_SERVER_ADDRESS='0.0.0.0' 39 | CMD ["./vulpo_server", "server"] 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ./guides/docs/01-quickstart.md -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 22.7.2-alpha -------------------------------------------------------------------------------- /admin/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /lib -------------------------------------------------------------------------------- /admin/rome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/rome/configuration_schema.json", 3 | "linter": { 4 | "enabled": true, 5 | "rules": { 6 | "recommended": true, 7 | "a11y": { 8 | "useKeyWithClickEvents": "off" 9 | }, 10 | "style": { 11 | "useConst": "off" 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /admin/src/app/router.tsx: -------------------------------------------------------------------------------- 1 | import { createBrowserRouter, Navigate, RouteObject } from "react-router-dom"; 2 | 3 | import { AppShell } from "@vulpo-dev/auth-admin-dashboard-core/component/app_shell"; 4 | import ProjectRedirect from "@vulpo-dev/auth-admin-dashboard-core/component/project_redirect"; 5 | 6 | import AuthPage from "@vulpo-dev/auth-admin-dashboard-core/pages/auth"; 7 | import SettingsPage from "@vulpo-dev/auth-admin-dashboard-core/pages/settings"; 8 | import UsersPage from "@vulpo-dev/auth-admin-dashboard-core/pages/users"; 9 | import AuthMethods from "@vulpo-dev/auth-admin-dashboard-core/pages/methods"; 10 | 11 | import Nav from "@vulpo-dev/auth-admin-dashboard-core/component/nav"; 12 | 13 | let routes: Array = [ 14 | { 15 | path: "auth/*", 16 | element: , 17 | }, 18 | { 19 | path: ":projectId/*", 20 | element: } />, 21 | children: [ 22 | UsersPage, 23 | SettingsPage, 24 | AuthMethods, 25 | { 26 | path: "*", 27 | element: , 28 | }, 29 | ], 30 | }, 31 | { 32 | path: "/*", 33 | element: , 34 | }, 35 | ]; 36 | 37 | export let Router = createBrowserRouter(routes, { 38 | basename: "/dashboard", 39 | }); 40 | -------------------------------------------------------------------------------- /admin/src/app/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import { setupListeners } from "@reduxjs/toolkit/dist/query"; 3 | 4 | import { adminApi } from "@vulpo-dev/auth-admin-dashboard-core/data/admin_api"; 5 | 6 | export const Store = configureStore({ 7 | reducer: { 8 | [adminApi.reducerPath]: adminApi.reducer, 9 | }, 10 | 11 | middleware: (getDefaultMiddleware) => 12 | getDefaultMiddleware().concat(adminApi.middleware), 13 | }); 14 | 15 | // Infer the `RootState` and `AppDispatch` types from the store itself 16 | export type RootState = ReturnType; 17 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} 18 | export type AppDispatch = typeof Store.dispatch; 19 | 20 | setupListeners(Store.dispatch); 21 | -------------------------------------------------------------------------------- /admin/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulpo-dev/auth/32c1189d03aec8fcf6888c6301bb8b633b03381b/admin/src/favicon.ico -------------------------------------------------------------------------------- /admin/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Auth - Vulpo 8 | 9 | 10 | 11 |
12 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /admin/src/main.tsx: -------------------------------------------------------------------------------- 1 | import "open-props/open-props.min.css"; 2 | import "werkbank/style/base.css"; 3 | import "./style.css"; 4 | 5 | import { createRoot } from "react-dom/client"; 6 | import { RouterProvider } from "react-router-dom"; 7 | import { Auth } from "@vulpo-dev/auth-react"; 8 | import { Provider } from "react-redux"; 9 | 10 | import { Router } from "./app/router"; 11 | import { Store } from "./app/store"; 12 | 13 | import { AuthClient } from "@vulpo-dev/auth-admin-dashboard-core/utils/auth"; 14 | 15 | function main() { 16 | let container = document.getElementById("root"); 17 | 18 | if (!container) { 19 | return; 20 | } 21 | 22 | let root = createRoot(container); 23 | 24 | root.render( 25 | 26 | 27 | 28 | 29 | , 30 | ); 31 | } 32 | 33 | main(); 34 | -------------------------------------------------------------------------------- /admin/src/types.d.ts: -------------------------------------------------------------------------------- 1 | import "redux-thunk/extend-redux"; 2 | 3 | declare global { 4 | interface Window { 5 | VULPO_ADMIN_ID: string; 6 | VULPO_ADMIN_BASE_URL?: string; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "declaration": true, 5 | "lib": [ 6 | "dom", 7 | "dom.iterable", 8 | "esnext" 9 | ], 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "strict": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "module": "esnext", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "jsx": "preserve" 22 | }, 23 | "exclude": ["./dist"] 24 | } 25 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | postgres: 5 | image: postgres 6 | container_name: vulpo_dev_db 7 | restart: always 8 | environment: 9 | POSTGRES_PASSWORD: postgres 10 | POSTGRES_USER: postgres 11 | ports: 12 | - 5432:5432 13 | volumes: 14 | - postgres-data:/var/lib/postgres 15 | 16 | mailhog: 17 | image: mailhog/mailhog 18 | container_name: vulpo_dev_mailhog 19 | restart: always 20 | ports: 21 | - 1025:1025 22 | - 8025:8025 23 | volumes: 24 | - mailhog-data:/var/lib/mailhog 25 | 26 | volumes: 27 | postgres-data: 28 | mailhog-data: 29 | -------------------------------------------------------------------------------- /example/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | PORT=5000 -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | /dist 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /example/.proxyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "/__/auth": { 3 | "target": "http://localhost:8000/" 4 | } 5 | } -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.14", 4 | "private": true, 5 | "source": "src/index.html", 6 | "dependencies": { 7 | "@biotic-ui/leptons": "^0.5.3", 8 | "@types/react": "^18.0.9", 9 | "@types/react-dom": "^18.0.3", 10 | "@vulpo-dev/auth-react": "^0.0.14", 11 | "@vulpo-dev/auth-sdk": "^0.0.14", 12 | "@vulpo-dev/auth-ui": "^0.0.20", 13 | "parcel": "^2.8.3", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "react-router-dom": "^6.7.0" 17 | }, 18 | "scripts": { 19 | "start": "parcel --port 5000 --hmr-port 5001" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/server/key.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqv7dXGHLWdP2EeshmnsA 3 | Wpd2JjkPYghoHtEg9PcpMA3zeHFwe/Qw//uojixXVXPJCvtYAnrNJ+U3GCHdthRX 4 | QdbJC3nMTKoeiyO6rjT9Uey6Nc7i6gfiMRihn87/pQwNMPlRj5bM6Gr2q4hX/WSJ 5 | up3Y/6W1GggpaXEQ8xW2tfL+hxOfAY2ZiOFVlVhHQIa9tq6OGyj1ZDDu3Hmnr82S 6 | GvJJ+B8OFp6E0ODEHZXh39cTe18vtVzZJUrgqDozWNuNAqnm8FxjTn4CtPZcdMU1 7 | 1RjwGULGxXFZCbrPmL2Ni/xWBCQ78Q8cFf1mlPs7tQ72rvP0r330MMw2MkTMzK1e 8 | mQIDAQAB 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /example/server/node_passport/index.js: -------------------------------------------------------------------------------- 1 | let express = require('express') 2 | let cors = require('cors') 3 | let fs = require('fs') 4 | let passport = require('passport') 5 | let JwtStrategy = require('passport-jwt').Strategy 6 | let ExtractJwt = require('passport-jwt').ExtractJwt 7 | 8 | let publicKey = fs.readFileSync('../key.pub', { encoding: 'utf8' }) 9 | 10 | let opts = { 11 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 12 | secretOrKey: publicKey, 13 | } 14 | 15 | passport.use(new JwtStrategy(opts, function(jwt_payload, done) { 16 | done(null, jwt_payload) 17 | })) 18 | 19 | let app = express() 20 | 21 | app.use(cors()) 22 | 23 | app.get('/', 24 | passport.authenticate('jwt', { session: false }), 25 | (req, res) => { res.json(req.user) } 26 | ) 27 | 28 | app.listen(8001, () => { 29 | console.log('Node Passport server is running') 30 | }) -------------------------------------------------------------------------------- /example/server/node_passport/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node_passport", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "cors": "^2.8.5" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/src/Auth.tsx: -------------------------------------------------------------------------------- 1 | import { Auth } from '@vulpo-dev/auth-sdk' 2 | 3 | let params = new URLSearchParams(window.location.search) 4 | let project = params.get('project') ?? 'ae16cc4a-33be-4b4e-a408-e67018fe453b' 5 | 6 | export default Auth.create({ 7 | project, 8 | baseURL: 'http://127.0.0.1:8000/api' 9 | }) 10 | -------------------------------------------------------------------------------- /example/src/index.css: -------------------------------------------------------------------------------- 1 | 2 | html, 3 | body, 4 | #root { 5 | height: 100%; 6 | } 7 | 8 | body { 9 | margin: 0; 10 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 11 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 12 | sans-serif; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | } 16 | 17 | code { 18 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 19 | monospace; 20 | } 21 | 22 | .authscreen { 23 | display: grid; 24 | grid-template-rows: 60px 1fr; 25 | height: 100%; 26 | } 27 | 28 | .authscreen-content--dark { 29 | background: #363848; 30 | } 31 | 32 | .authscreen-header { 33 | border-bottom: 5px solid #000; 34 | display: flex; 35 | align-items: center; 36 | justify-content: space-between; 37 | padding: 0 2rem; 38 | } 39 | 40 | .vulpo-auth-container { 41 | 42 | } -------------------------------------------------------------------------------- /example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Auth - Vulpo 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /example/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css'; 4 | import '@vulpo-dev/auth-ui/styles.css' 5 | import Bootstrap from './Bootstrap'; 6 | import { BrowserRouter } from 'react-router-dom' 7 | import { Auth as AuthCtx } from '@vulpo-dev/auth-react' 8 | import AuthClient from './Auth' 9 | 10 | let root = createRoot( 11 | document.getElementById('root')! 12 | ) 13 | 14 | root.render( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "declaration": true, 5 | "lib": [ 6 | "dom", 7 | "dom.iterable", 8 | "esnext" 9 | ], 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "strict": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "module": "esnext", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "jsx": "preserve" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /guides/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /guides/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /guides/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /guides/docs/02-install/_category.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Install", 3 | "link": { 4 | "type": "generated-index", 5 | "description": "Server setup." 6 | } 7 | } -------------------------------------------------------------------------------- /guides/docs/03-develop/04-passwordless-verify-reset.md: -------------------------------------------------------------------------------- 1 | # Passwordless, Verify Email, and Password Reset 2 | 3 | Passwordless, Verify Email, and Password Reset have one thing in common: email. Before you can enable these features, you need to configure your email settings in the admin dashboard, under the settings tab. Once you have your email set up, you can go to `Sign In Methods` and enable `Authentication Link`, `Reset Password`, and `Verify Email`. 4 | 5 | **Note**: In most cases, you will use port 587 in your SMTP settings. 6 | 7 | ## SMTP Relay Provider 8 | - [Tipimail](https://www.tipimail.com/) 9 | - [EmailLabs](https://emaillabs.io) 10 | - [Postmark](https://postmarkapp.com) 11 | - [SMTP](https://www.smtp.com/) 12 | - [mailgun](https://www.mailgun.com/) 13 | - [SendGrid](https://sendgrid.com/) -------------------------------------------------------------------------------- /guides/docs/03-develop/06-update-email.md: -------------------------------------------------------------------------------- 1 | # Update a users email 2 | 3 | ## Internals 4 | 5 | The following flow happens when you update a users email address: 6 | 1. The request to change the email will be stored to in the database. 7 | 2. Two emails will be send out: 8 | 1. The old email address will be informed of the request with an option to reset/undo the change. 9 | 2. The new email address will receive an email in order to confirm the new address. 10 | 3. Once the new email address has been confimed, the user will be updated with the new email address. 11 | 12 | **Note:** once the flow has been rejected, a new request has to be made. -------------------------------------------------------------------------------- /guides/docs/03-develop/07-api-keys.md: -------------------------------------------------------------------------------- 1 | # API Keys 2 | 3 | 4 | ## Generate an API Key 5 | 6 | The [client SDK](https://auth.vulpo.dev/docs/web/overview) provides the `generateApiKey` method, which takes an optional configuration object with the following properties: 7 | 8 | - `name`: A name for the API key. 9 | - `expireAt`: A timestamp for when the API key should expire. 10 | 11 | 12 | ## Validate Requests 13 | 14 | To validate an API key, make a POST request to the `/api_key/verify` endpoint with the following payload: 15 | 16 | ```json 17 | { 18 | "api_key": "" 19 | } 20 | ``` 21 | 22 | If the API key is valid, a claims object will be returned: 23 | ```ts 24 | type Claims = { 25 | sub: string; // the user id 26 | exp: number, // timestamp until the claims expire 27 | traits: Array, // traits given to the user 28 | } 29 | ``` 30 | 31 | If the token is invalid, a `401` status will be returned. 32 | 33 | You can find an example in the test suite: https://github.com/vulpo-dev/auth/blob/master/tests/api_tests/src/api_key/verify.test.ts -------------------------------------------------------------------------------- /guides/docs/03-develop/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Develop", 3 | "link": { 4 | "type": "generated-index", 5 | "title": "Develop Overview" 6 | } 7 | } -------------------------------------------------------------------------------- /guides/docs/04-server/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Server", 3 | "link": { 4 | "type": "generated-index", 5 | "title": "Server Overview" 6 | } 7 | } -------------------------------------------------------------------------------- /guides/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | { 23 | type: 'category', 24 | label: 'Tutorial', 25 | items: ['hello'], 26 | }, 27 | ], 28 | */ 29 | }; 30 | 31 | module.exports = sidebars; 32 | -------------------------------------------------------------------------------- /guides/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulpo-dev/auth/32c1189d03aec8fcf6888c6301bb8b633b03381b/guides/static/.nojekyll -------------------------------------------------------------------------------- /guides/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulpo-dev/auth/32c1189d03aec8fcf6888c6301bb8b633b03381b/guides/static/img/docusaurus.png -------------------------------------------------------------------------------- /guides/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulpo-dev/auth/32c1189d03aec8fcf6888c6301bb8b633b03381b/guides/static/img/favicon.ico -------------------------------------------------------------------------------- /guides/static/js/plausible.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | let doNotTrack = ( 3 | navigator.doNotTrack === '1' || 4 | window.location.hostname === 'localhost' 5 | ) 6 | 7 | if (doNotTrack) { 8 | return 9 | } 10 | 11 | let pirsch = document.createElement('script') 12 | pirsch.setAttribute('src', 'https://api.pirsch.io/pirsch.js') 13 | pirsch.setAttribute('data-code', 'Sm6vcDWFZdo3UVgWd9FpRmkfh3SgLgEG') 14 | pirsch.setAttribute('id', 'pirschjs') 15 | pirsch.setAttribute('defer', '') 16 | pirsch.setAttribute('type', 'text/javascript') 17 | 18 | window.document.head.appendChild(pirsch) 19 | }()); 20 | -------------------------------------------------------------------------------- /guides/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@tsconfig/docusaurus/tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "independent", 3 | "npmClient": "npm", 4 | "useWorkspaces": true 5 | } 6 | -------------------------------------------------------------------------------- /packages/admin/.gitignore: -------------------------------------------------------------------------------- 1 | /component 2 | /data 3 | /pages 4 | /utils 5 | /types.d.js -------------------------------------------------------------------------------- /packages/admin/.swcrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/swcrc", 3 | "jsc": { 4 | "parser": { 5 | "syntax": "typescript", 6 | "jsx": true, 7 | "tsx": true, 8 | "dynamicImport": false, 9 | "privateMethod": false, 10 | "functionBind": false, 11 | "exportDefaultFrom": false, 12 | "exportNamespaceFrom": false, 13 | "decorators": false, 14 | "decoratorsBeforeExport": false, 15 | "topLevelAwait": false, 16 | "importMeta": false 17 | }, 18 | "transform": { 19 | "react": { 20 | "runtime": "automatic" 21 | } 22 | }, 23 | "target": "es2022", 24 | "loose": false, 25 | "externalHelpers": false, 26 | "keepClassNames": false 27 | }, 28 | "minify": false 29 | } 30 | -------------------------------------------------------------------------------- /packages/admin/rome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/rome/configuration_schema.json", 3 | "linter": { 4 | "enabled": true, 5 | "rules": { 6 | "recommended": true, 7 | "a11y": { 8 | "useKeyWithClickEvents": "off" 9 | }, 10 | "style": { 11 | "useConst": "off" 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /packages/admin/scripts/clean.mjs: -------------------------------------------------------------------------------- 1 | import * as fs from "fs/promises"; 2 | import * as path from "path"; 3 | import { existsSync } from "fs"; 4 | 5 | async function main() { 6 | let items = await fs.readdir( 7 | path.join(process.cwd(), "src") 8 | ) 9 | 10 | let sink = [...items, "types.d.js"].map(async item => { 11 | let filePath = path.join(process.cwd(), item); 12 | console.log('Delete: ', filePath) 13 | 14 | if (existsSync(filePath) === false) { 15 | return 16 | } 17 | 18 | return fs.rm(filePath, { recursive: true }); 19 | }) 20 | 21 | await Promise.all(sink); 22 | } 23 | 24 | main().catch(err => { 25 | console.log('Something went wrong'); 26 | console.log(err); 27 | process.exit(1); 28 | }); -------------------------------------------------------------------------------- /packages/admin/src/component/button.ts: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled"; 2 | import { Button } from "werkbank/component/button"; 3 | 4 | export { Button } from "werkbank/component/button"; 5 | 6 | export let WarnButton = styled(Button)` 7 | --button-bg: var(--color-warn); 8 | --button-bg--hover: var(--color-warn--light); 9 | font-weight: bold; 10 | `; 11 | -------------------------------------------------------------------------------- /packages/admin/src/component/page.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled"; 2 | 3 | import { TopBar } from "werkbank/component/layout"; 4 | 5 | export let PageWrapper = styled(TopBar.Container)``; 6 | export let PageContent = styled(TopBar.Content)``; 7 | 8 | export let PageHeader = styled(TopBar.Header)` 9 | border-bottom: var(--border); 10 | background: var(--color-background--dark); 11 | `; 12 | 13 | export let PageTitle = styled.h1` 14 | margin: 0; 15 | font-size: var(--size-4); 16 | display: flex; 17 | gap: var(--size-2); 18 | align-items: center; 19 | `; 20 | -------------------------------------------------------------------------------- /packages/admin/src/component/project_redirect.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | import { Navigate, useNavigate } from "react-router-dom"; 3 | import { useGetProjectsQuery } from "../data/admin_api"; 4 | import { isUuid, useCurrentProject } from "../data/project"; 5 | 6 | import { useAuthStateChange } from "@vulpo-dev/auth-react"; 7 | 8 | let ProjectRedirect = () => { 9 | let projectId = useCurrentProject(); 10 | let { data: projects } = useGetProjectsQuery([]); 11 | let navigate = useNavigate(); 12 | 13 | let mounted = useRef(true); 14 | useEffect(() => { 15 | mounted.current = true; 16 | return () => { 17 | mounted.current = false; 18 | }; 19 | }); 20 | 21 | useAuthStateChange((session) => { 22 | if (mounted.current && session === null) { 23 | navigate("auth/", { replace: true }); 24 | } 25 | }); 26 | 27 | if (projectId === "project") { 28 | return null; 29 | } 30 | 31 | let validUuid = isUuid.test(projectId ?? ""); 32 | let project = projects?.at(0); 33 | if (!validUuid && project) { 34 | return ; 35 | } 36 | 37 | return null; 38 | }; 39 | 40 | export default ProjectRedirect; 41 | -------------------------------------------------------------------------------- /packages/admin/src/component/text.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled"; 2 | 3 | export let ErrorText = styled.p` 4 | color: var(--color-error); 5 | `; 6 | -------------------------------------------------------------------------------- /packages/admin/src/data/project.ts: -------------------------------------------------------------------------------- 1 | import { useMatches } from "react-router-dom"; 2 | 3 | // https://stackoverflow.com/a/38191104 4 | export let isUuid = new RegExp( 5 | /^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, 6 | ); 7 | 8 | export function useCurrentProject(): string | null { 9 | let matches = useMatches(); 10 | let match = matches.find((match) => { 11 | return match.params.projectId; 12 | }); 13 | 14 | let id = match?.params?.projectId; 15 | 16 | if (!id) { 17 | return null; 18 | } 19 | 20 | return id; 21 | } 22 | 23 | export function useActiveProject(): string { 24 | let projectId = useCurrentProject(); 25 | 26 | if (!projectId) { 27 | throw new Error("useActiveProject used outside of project route"); 28 | } 29 | 30 | return projectId; 31 | } 32 | -------------------------------------------------------------------------------- /packages/admin/src/pages/auth/index.ts: -------------------------------------------------------------------------------- 1 | export { AuthPage as default } from "./auth"; 2 | -------------------------------------------------------------------------------- /packages/admin/src/pages/methods/index.tsx: -------------------------------------------------------------------------------- 1 | import { RouteObject } from "react-router-dom"; 2 | import { AuthMethods } from "./methods"; 3 | 4 | let Route: RouteObject = { 5 | path: "auth-methods", 6 | element: , 7 | }; 8 | 9 | export default Route; 10 | -------------------------------------------------------------------------------- /packages/admin/src/pages/settings/component/layout.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled"; 2 | 3 | export let Header = styled.header` 4 | display: flex; 5 | justify-content: space-between; 6 | align-items: center; 7 | margin-bottom: var(--size-3); 8 | 9 | h2 { 10 | margin-bottom: 0; 11 | } 12 | `; 13 | 14 | export let Section = styled.section` 15 | border-radius: var(--size-2); 16 | background: var(--color-background--dark); 17 | padding: var(--size-5); 18 | max-width: var(--container-width); 19 | margin-bottom: var(--size-5); 20 | width: 100%; 21 | `; 22 | -------------------------------------------------------------------------------- /packages/admin/src/pages/settings/component/public_keys.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Textarea, 3 | Section as InputSection, 4 | Label, 5 | Input, 6 | } from "werkbank/component/form"; 7 | 8 | import { Header, Section } from "./layout"; 9 | import { useGetPublicKeysQuery } from "../../../data/admin_api"; 10 | import styled from "@emotion/styled"; 11 | 12 | type Props = { 13 | project: string; 14 | }; 15 | 16 | let PublicKeysSettings = ({ project }: Props) => { 17 | let keys = useGetPublicKeysQuery([project]); 18 | 19 | if (keys.data === undefined) { 20 | return null; 21 | } 22 | 23 | return ( 24 |
25 |
26 |
27 |

Public Keys

28 |
29 | {keys.data.map((key) => ( 30 | 31 | 32 | 33 |