@tinyrack/issuary-server 0.31.0 → 0.32.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entities/auth-budget.entity.d.ts +20 -0
- package/dist/entities/auth-budget.entity.d.ts.map +1 -0
- package/dist/entities/auth-budget.entity.js +11 -0
- package/dist/entities/auth-budget.entity.js.map +1 -0
- package/dist/entities/browser-session.entity.d.ts +32 -0
- package/dist/entities/browser-session.entity.d.ts.map +1 -0
- package/dist/entities/browser-session.entity.js +12 -0
- package/dist/entities/browser-session.entity.js.map +1 -0
- package/dist/entities/email-verification.entity.d.ts +18 -0
- package/dist/entities/email-verification.entity.d.ts.map +1 -1
- package/dist/entities/email-verification.entity.js +2 -0
- package/dist/entities/email-verification.entity.js.map +1 -1
- package/dist/entities/oauth-client.entity.d.ts +120 -0
- package/dist/entities/oauth-client.entity.d.ts.map +1 -1
- package/dist/entities/oauth-code.entity.d.ts +120 -0
- package/dist/entities/oauth-code.entity.d.ts.map +1 -1
- package/dist/entities/oauth-code.entity.js +3 -0
- package/dist/entities/oauth-code.entity.js.map +1 -1
- package/dist/entities/oauth-device-code.entity.d.ts +120 -0
- package/dist/entities/oauth-device-code.entity.d.ts.map +1 -1
- package/dist/entities/oauth-device-code.entity.js +3 -0
- package/dist/entities/oauth-device-code.entity.js.map +1 -1
- package/dist/entities/oauth-grant.entity.d.ts +52 -0
- package/dist/entities/oauth-grant.entity.d.ts.map +1 -0
- package/dist/entities/oauth-grant.entity.js +19 -0
- package/dist/entities/oauth-grant.entity.js.map +1 -0
- package/dist/entities/password-reset.entity.d.ts +18 -0
- package/dist/entities/password-reset.entity.d.ts.map +1 -1
- package/dist/entities/password-reset.entity.js +2 -0
- package/dist/entities/password-reset.entity.js.map +1 -1
- package/dist/entities/pending-oauth-registration.entity.d.ts +9 -0
- package/dist/entities/pending-oauth-registration.entity.d.ts.map +1 -1
- package/dist/entities/pending-oauth-registration.entity.js +1 -0
- package/dist/entities/pending-oauth-registration.entity.js.map +1 -1
- package/dist/entities/revoked-token.entity.d.ts +120 -0
- package/dist/entities/revoked-token.entity.d.ts.map +1 -1
- package/dist/entities/terms-content.entity.d.ts +36 -0
- package/dist/entities/terms-content.entity.d.ts.map +1 -1
- package/dist/entities/terms.entity.d.ts +36 -0
- package/dist/entities/terms.entity.d.ts.map +1 -1
- package/dist/entities/user-consent.entity.d.ts +120 -0
- package/dist/entities/user-consent.entity.d.ts.map +1 -1
- package/dist/entities/user-terms-consent.entity.d.ts +36 -0
- package/dist/entities/user-terms-consent.entity.d.ts.map +1 -1
- package/dist/entities/user-totp-recovery-code.entity.d.ts +36 -0
- package/dist/entities/user-totp-recovery-code.entity.d.ts.map +1 -1
- package/dist/entities/user-totp.entity.d.ts +36 -0
- package/dist/entities/user-totp.entity.d.ts.map +1 -1
- package/dist/entities/user-totp.entity.js +1 -0
- package/dist/entities/user-totp.entity.js.map +1 -1
- package/dist/entities/user.entity.d.ts +36 -0
- package/dist/entities/user.entity.d.ts.map +1 -1
- package/dist/entities/user.entity.js +3 -0
- package/dist/entities/user.entity.js.map +1 -1
- package/dist/entrypoints/app.d.ts +448 -437
- package/dist/entrypoints/app.d.ts.map +1 -1
- package/dist/entrypoints/app.js +78 -6
- package/dist/entrypoints/app.js.map +1 -1
- package/dist/entrypoints/database/postgres/compiled-functions.d.ts +326 -200
- package/dist/entrypoints/database/postgres/compiled-functions.js +1849 -518
- package/dist/entrypoints/database/postgres/compiled-functions.js.map +1 -1
- package/dist/entrypoints/database/sqlite/compiled-functions.d.ts +326 -200
- package/dist/entrypoints/database/sqlite/compiled-functions.js +1849 -518
- package/dist/entrypoints/database/sqlite/compiled-functions.js.map +1 -1
- package/dist/entrypoints/database/sqlite/sqlite.d.ts.map +1 -1
- package/dist/entrypoints/database/sqlite/sqlite.js +1 -0
- package/dist/entrypoints/database/sqlite/sqlite.js.map +1 -1
- package/dist/entrypoints/scheduler/database.d.ts +3 -1
- package/dist/entrypoints/scheduler/database.d.ts.map +1 -1
- package/dist/entrypoints/scheduler/database.js +13 -2
- package/dist/entrypoints/scheduler/database.js.map +1 -1
- package/dist/entrypoints/scheduler/distributed-runner.d.ts +4 -0
- package/dist/entrypoints/scheduler/distributed-runner.d.ts.map +1 -1
- package/dist/entrypoints/scheduler/distributed-runner.js +26 -1
- package/dist/entrypoints/scheduler/distributed-runner.js.map +1 -1
- package/dist/lib/config/email.d.ts +1 -0
- package/dist/lib/config/email.d.ts.map +1 -1
- package/dist/lib/config/email.js.map +1 -1
- package/dist/lib/database/entities.d.ts.map +1 -1
- package/dist/lib/database/entities.js +6 -0
- package/dist/lib/database/entities.js.map +1 -1
- package/dist/lib/openapi.d.ts +2 -0
- package/dist/lib/openapi.d.ts.map +1 -1
- package/dist/lib/openapi.js +36 -1
- package/dist/lib/openapi.js.map +1 -1
- package/dist/lib/request-ip.d.ts +4 -0
- package/dist/lib/request-ip.d.ts.map +1 -0
- package/dist/lib/request-ip.js +39 -0
- package/dist/lib/request-ip.js.map +1 -0
- package/dist/middleware/auth-budget.d.ts +3 -0
- package/dist/middleware/auth-budget.d.ts.map +1 -0
- package/dist/middleware/auth-budget.js +80 -0
- package/dist/middleware/auth-budget.js.map +1 -0
- package/dist/middleware/session.d.ts +23 -5
- package/dist/middleware/session.d.ts.map +1 -1
- package/dist/middleware/session.js +152 -12
- package/dist/middleware/session.js.map +1 -1
- package/dist/middleware/strict-input.d.ts +4 -0
- package/dist/middleware/strict-input.d.ts.map +1 -0
- package/dist/middleware/strict-input.js +144 -0
- package/dist/middleware/strict-input.js.map +1 -0
- package/dist/migrations/postgres/Migration20260910120000_api_security.d.ts +6 -0
- package/dist/migrations/postgres/Migration20260910120000_api_security.d.ts.map +1 -0
- package/dist/migrations/postgres/Migration20260910120000_api_security.js +20 -0
- package/dist/migrations/postgres/Migration20260910120000_api_security.js.map +1 -0
- package/dist/migrations/postgres/Migration20260910160000_security_followup.d.ts +6 -0
- package/dist/migrations/postgres/Migration20260910160000_security_followup.d.ts.map +1 -0
- package/dist/migrations/postgres/Migration20260910160000_security_followup.js +23 -0
- package/dist/migrations/postgres/Migration20260910160000_security_followup.js.map +1 -0
- package/dist/migrations/postgres/Migration20260910200000_authentication_epoch.d.ts +6 -0
- package/dist/migrations/postgres/Migration20260910200000_authentication_epoch.d.ts.map +1 -0
- package/dist/migrations/postgres/Migration20260910200000_authentication_epoch.js +28 -0
- package/dist/migrations/postgres/Migration20260910200000_authentication_epoch.js.map +1 -0
- package/dist/migrations/postgres/Migration20260910220000_flow_revocation.d.ts +6 -0
- package/dist/migrations/postgres/Migration20260910220000_flow_revocation.d.ts.map +1 -0
- package/dist/migrations/postgres/Migration20260910220000_flow_revocation.js +19 -0
- package/dist/migrations/postgres/Migration20260910220000_flow_revocation.js.map +1 -0
- package/dist/migrations/postgres/index.d.ts.map +1 -1
- package/dist/migrations/postgres/index.js +8 -0
- package/dist/migrations/postgres/index.js.map +1 -1
- package/dist/migrations/sqlite/Migration20260910120000_api_security.d.ts +6 -0
- package/dist/migrations/sqlite/Migration20260910120000_api_security.d.ts.map +1 -0
- package/dist/migrations/sqlite/Migration20260910120000_api_security.js +20 -0
- package/dist/migrations/sqlite/Migration20260910120000_api_security.js.map +1 -0
- package/dist/migrations/sqlite/Migration20260910160000_security_followup.d.ts +6 -0
- package/dist/migrations/sqlite/Migration20260910160000_security_followup.d.ts.map +1 -0
- package/dist/migrations/sqlite/Migration20260910160000_security_followup.js +23 -0
- package/dist/migrations/sqlite/Migration20260910160000_security_followup.js.map +1 -0
- package/dist/migrations/sqlite/Migration20260910200000_authentication_epoch.d.ts +8 -0
- package/dist/migrations/sqlite/Migration20260910200000_authentication_epoch.d.ts.map +1 -0
- package/dist/migrations/sqlite/Migration20260910200000_authentication_epoch.js +72 -0
- package/dist/migrations/sqlite/Migration20260910200000_authentication_epoch.js.map +1 -0
- package/dist/migrations/sqlite/Migration20260910220000_flow_revocation.d.ts +6 -0
- package/dist/migrations/sqlite/Migration20260910220000_flow_revocation.d.ts.map +1 -0
- package/dist/migrations/sqlite/Migration20260910220000_flow_revocation.js +19 -0
- package/dist/migrations/sqlite/Migration20260910220000_flow_revocation.js.map +1 -0
- package/dist/migrations/sqlite/index.d.ts.map +1 -1
- package/dist/migrations/sqlite/index.js +8 -0
- package/dist/migrations/sqlite/index.js.map +1 -1
- package/dist/repositories/email-verification.repository.d.ts +1 -0
- package/dist/repositories/email-verification.repository.d.ts.map +1 -1
- package/dist/repositories/email-verification.repository.js +8 -17
- package/dist/repositories/email-verification.repository.js.map +1 -1
- package/dist/repositories/oauth-code.repository.d.ts +2 -0
- package/dist/repositories/oauth-code.repository.d.ts.map +1 -1
- package/dist/repositories/oauth-code.repository.js +8 -0
- package/dist/repositories/oauth-code.repository.js.map +1 -1
- package/dist/repositories/oauth-device-code.repository.d.ts +3 -0
- package/dist/repositories/oauth-device-code.repository.d.ts.map +1 -1
- package/dist/repositories/oauth-device-code.repository.js +64 -29
- package/dist/repositories/oauth-device-code.repository.js.map +1 -1
- package/dist/repositories/password-reset.repository.d.ts +1 -0
- package/dist/repositories/password-reset.repository.d.ts.map +1 -1
- package/dist/repositories/password-reset.repository.js +6 -19
- package/dist/repositories/password-reset.repository.js.map +1 -1
- package/dist/repositories/pending-oauth-registration.repository.d.ts +2 -0
- package/dist/repositories/pending-oauth-registration.repository.d.ts.map +1 -1
- package/dist/repositories/pending-oauth-registration.repository.js +16 -2
- package/dist/repositories/pending-oauth-registration.repository.js.map +1 -1
- package/dist/repositories/terms.repository.d.ts.map +1 -1
- package/dist/repositories/terms.repository.js +1 -0
- package/dist/repositories/terms.repository.js.map +1 -1
- package/dist/repositories/user-consent.repository.d.ts.map +1 -1
- package/dist/repositories/user-consent.repository.js +1 -1
- package/dist/repositories/user-consent.repository.js.map +1 -1
- package/dist/repositories/user-terms-consent.repository.d.ts.map +1 -1
- package/dist/repositories/user-terms-consent.repository.js +2 -0
- package/dist/repositories/user-terms-consent.repository.js.map +1 -1
- package/dist/repositories/user-totp-recovery-code.repository.d.ts +24 -0
- package/dist/repositories/user-totp-recovery-code.repository.d.ts.map +1 -1
- package/dist/repositories/user-totp.repository.d.ts +36 -0
- package/dist/repositories/user-totp.repository.d.ts.map +1 -1
- package/dist/routes/.well-known/openid-configuration/get.d.ts.map +1 -1
- package/dist/routes/.well-known/openid-configuration/get.js +5 -1
- package/dist/routes/.well-known/openid-configuration/get.js.map +1 -1
- package/dist/routes/api/admin/console/index.d.ts.map +1 -1
- package/dist/routes/api/admin/console/index.js +29 -14
- package/dist/routes/api/admin/console/index.js.map +1 -1
- package/dist/routes/api/admin/me/get.js +2 -3
- package/dist/routes/api/admin/me/get.js.map +1 -1
- package/dist/routes/api/admin/users/index.d.ts.map +1 -1
- package/dist/routes/api/admin/users/index.js +59 -52
- package/dist/routes/api/admin/users/index.js.map +1 -1
- package/dist/routes/api/auth/accounts/remove.post.d.ts +5 -5
- package/dist/routes/api/auth/email/resend/post.d.ts.map +1 -1
- package/dist/routes/api/auth/email/resend/post.js +3 -24
- package/dist/routes/api/auth/email/resend/post.js.map +1 -1
- package/dist/routes/api/auth/email/verify/post.d.ts.map +1 -1
- package/dist/routes/api/auth/email/verify/post.js +29 -12
- package/dist/routes/api/auth/email/verify/post.js.map +1 -1
- package/dist/routes/api/auth/login/post.d.ts.map +1 -1
- package/dist/routes/api/auth/login/post.js +4 -3
- package/dist/routes/api/auth/login/post.js.map +1 -1
- package/dist/routes/api/auth/passkey/verify/post.d.ts.map +1 -1
- package/dist/routes/api/auth/passkey/verify/post.js +25 -11
- package/dist/routes/api/auth/passkey/verify/post.js.map +1 -1
- package/dist/routes/api/auth/password/forgot/post.d.ts.map +1 -1
- package/dist/routes/api/auth/password/forgot/post.js +2 -19
- package/dist/routes/api/auth/password/forgot/post.js.map +1 -1
- package/dist/routes/api/auth/register/post.d.ts.map +1 -1
- package/dist/routes/api/auth/register/post.js +4 -3
- package/dist/routes/api/auth/register/post.js.map +1 -1
- package/dist/routes/api/auth/totp/recovery/verify/post.d.ts.map +1 -1
- package/dist/routes/api/auth/totp/recovery/verify/post.js +7 -5
- package/dist/routes/api/auth/totp/recovery/verify/post.js.map +1 -1
- package/dist/routes/api/auth/totp/verify/post.d.ts.map +1 -1
- package/dist/routes/api/auth/totp/verify/post.js +7 -5
- package/dist/routes/api/auth/totp/verify/post.js.map +1 -1
- package/dist/routes/api/consent/post.d.ts.map +1 -1
- package/dist/routes/api/consent/post.js +141 -120
- package/dist/routes/api/consent/post.js.map +1 -1
- package/dist/routes/api/docs/get.d.ts.map +1 -1
- package/dist/routes/api/docs/get.js +8 -1
- package/dist/routes/api/docs/get.js.map +1 -1
- package/dist/routes/api/index.d.ts +379 -379
- package/dist/routes/api/oauth/_provider/authorize/get.d.ts.map +1 -1
- package/dist/routes/api/oauth/_provider/authorize/get.js +5 -1
- package/dist/routes/api/oauth/_provider/authorize/get.js.map +1 -1
- package/dist/routes/api/oauth/_provider/callback/get.d.ts +9 -9
- package/dist/routes/api/oauth/_provider/callback/get.d.ts.map +1 -1
- package/dist/routes/api/oauth/_provider/callback/get.js +30 -6
- package/dist/routes/api/oauth/_provider/callback/get.js.map +1 -1
- package/dist/routes/api/oauth/_provider/callback/post.d.ts.map +1 -1
- package/dist/routes/api/oauth/_provider/callback/post.js +31 -31
- package/dist/routes/api/oauth/_provider/callback/post.js.map +1 -1
- package/dist/routes/api/oauth/_provider/delete.d.ts.map +1 -1
- package/dist/routes/api/oauth/_provider/delete.js +14 -11
- package/dist/routes/api/oauth/_provider/delete.js.map +1 -1
- package/dist/routes/api/oauth/index.d.ts +18 -18
- package/dist/routes/api/terms/consent/post.d.ts.map +1 -1
- package/dist/routes/api/terms/consent/post.js +59 -43
- package/dist/routes/api/terms/consent/post.js.map +1 -1
- package/dist/routes/api/user/delete.d.ts.map +1 -1
- package/dist/routes/api/user/delete.js +8 -8
- package/dist/routes/api/user/delete.js.map +1 -1
- package/dist/routes/api/user/passkeys/_id/delete.d.ts.map +1 -1
- package/dist/routes/api/user/passkeys/_id/delete.js +17 -29
- package/dist/routes/api/user/passkeys/_id/delete.js.map +1 -1
- package/dist/routes/api/user/passkeys/_id/patch.d.ts.map +1 -1
- package/dist/routes/api/user/passkeys/_id/patch.js +16 -13
- package/dist/routes/api/user/passkeys/_id/patch.js.map +1 -1
- package/dist/routes/api/user/passkeys/register/verify/post.d.ts.map +1 -1
- package/dist/routes/api/user/passkeys/register/verify/post.js +27 -19
- package/dist/routes/api/user/passkeys/register/verify/post.js.map +1 -1
- package/dist/routes/api/user/password/delete.d.ts.map +1 -1
- package/dist/routes/api/user/password/delete.js +13 -10
- package/dist/routes/api/user/password/delete.js.map +1 -1
- package/dist/routes/api/user/password/post.d.ts.map +1 -1
- package/dist/routes/api/user/password/post.js +13 -10
- package/dist/routes/api/user/password/post.js.map +1 -1
- package/dist/routes/api/user/password/put.d.ts.map +1 -1
- package/dist/routes/api/user/password/put.js +13 -10
- package/dist/routes/api/user/password/put.js.map +1 -1
- package/dist/routes/api/user/totp/confirm/post.d.ts.map +1 -1
- package/dist/routes/api/user/totp/confirm/post.js +33 -18
- package/dist/routes/api/user/totp/confirm/post.js.map +1 -1
- package/dist/routes/api/user/totp/delete.d.ts.map +1 -1
- package/dist/routes/api/user/totp/delete.js +13 -18
- package/dist/routes/api/user/totp/delete.js.map +1 -1
- package/dist/routes/api/user/totp/recovery/regenerate/post.d.ts.map +1 -1
- package/dist/routes/api/user/totp/recovery/regenerate/post.js +13 -10
- package/dist/routes/api/user/totp/recovery/regenerate/post.js.map +1 -1
- package/dist/routes/api/user/totp/setup/post.d.ts.map +1 -1
- package/dist/routes/api/user/totp/setup/post.js +13 -8
- package/dist/routes/api/user/totp/setup/post.js.map +1 -1
- package/dist/routes/api/user/totp/verify/post.d.ts.map +1 -1
- package/dist/routes/api/user/totp/verify/post.js +23 -12
- package/dist/routes/api/user/totp/verify/post.js.map +1 -1
- package/dist/routes/index.d.ts +252 -242
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/oauth/authorize/get.d.ts.map +1 -1
- package/dist/routes/oauth/authorize/get.js +12 -4
- package/dist/routes/oauth/authorize/get.js.map +1 -1
- package/dist/routes/oauth/device/get-post.d.ts +10 -0
- package/dist/routes/oauth/device/get-post.d.ts.map +1 -1
- package/dist/routes/oauth/device/get-post.js +32 -3
- package/dist/routes/oauth/device/get-post.js.map +1 -1
- package/dist/routes/oauth/device-authorization/post.d.ts.map +1 -1
- package/dist/routes/oauth/device-authorization/post.js +13 -7
- package/dist/routes/oauth/device-authorization/post.js.map +1 -1
- package/dist/routes/oauth/end-session/get.d.ts.map +1 -1
- package/dist/routes/oauth/end-session/get.js +2 -3
- package/dist/routes/oauth/end-session/get.js.map +1 -1
- package/dist/routes/oauth/index.d.ts +10 -0
- package/dist/routes/oauth/index.d.ts.map +1 -1
- package/dist/routes/oauth/revoke/post.d.ts.map +1 -1
- package/dist/routes/oauth/revoke/post.js +10 -2
- package/dist/routes/oauth/revoke/post.js.map +1 -1
- package/dist/routes/oauth/token/post.d.ts.map +1 -1
- package/dist/routes/oauth/token/post.js +69 -57
- package/dist/routes/oauth/token/post.js.map +1 -1
- package/dist/schemas/error.d.ts +75 -0
- package/dist/schemas/error.d.ts.map +1 -1
- package/dist/schemas/error.js +3 -0
- package/dist/schemas/error.js.map +1 -1
- package/dist/schemas/field.d.ts.map +1 -1
- package/dist/schemas/field.js +3 -0
- package/dist/schemas/field.js.map +1 -1
- package/dist/seeders/config.seeder.d.ts +1 -1
- package/dist/seeders/config.seeder.d.ts.map +1 -1
- package/dist/seeders/config.seeder.js +100 -36
- package/dist/seeders/config.seeder.js.map +1 -1
- package/dist/services/admin-console.service.d.ts.map +1 -1
- package/dist/services/admin-console.service.js +137 -100
- package/dist/services/admin-console.service.js.map +1 -1
- package/dist/services/auth-budget.service.d.ts +6 -0
- package/dist/services/auth-budget.service.d.ts.map +1 -0
- package/dist/services/auth-budget.service.js +30 -0
- package/dist/services/auth-budget.service.js.map +1 -0
- package/dist/services/authentication-epoch.d.ts +5 -0
- package/dist/services/authentication-epoch.d.ts.map +1 -0
- package/dist/services/authentication-epoch.js +22 -0
- package/dist/services/authentication-epoch.js.map +1 -0
- package/dist/services/authorization-browser.service.d.ts +18 -0
- package/dist/services/authorization-browser.service.d.ts.map +1 -0
- package/dist/services/authorization-browser.service.js +28 -0
- package/dist/services/authorization-browser.service.js.map +1 -0
- package/dist/services/browser-security.service.d.ts +14 -0
- package/dist/services/browser-security.service.d.ts.map +1 -0
- package/dist/services/browser-security.service.js +85 -0
- package/dist/services/browser-security.service.js.map +1 -0
- package/dist/services/browser-session.service.d.ts +25 -0
- package/dist/services/browser-session.service.d.ts.map +1 -0
- package/dist/services/browser-session.service.js +102 -0
- package/dist/services/browser-session.service.js.map +1 -0
- package/dist/services/cleanup.service.d.ts +1 -0
- package/dist/services/cleanup.service.d.ts.map +1 -1
- package/dist/services/cleanup.service.js +31 -0
- package/dist/services/cleanup.service.js.map +1 -1
- package/dist/services/client-security.d.ts +641 -0
- package/dist/services/client-security.d.ts.map +1 -0
- package/dist/services/client-security.js +10 -0
- package/dist/services/client-security.js.map +1 -0
- package/dist/services/container.d.ts +2 -0
- package/dist/services/container.d.ts.map +1 -1
- package/dist/services/container.js +6 -2
- package/dist/services/container.js.map +1 -1
- package/dist/services/email.service.d.ts +2 -14
- package/dist/services/email.service.d.ts.map +1 -1
- package/dist/services/email.service.js +35 -29
- package/dist/services/email.service.js.map +1 -1
- package/dist/services/jwt.service.d.ts +1 -0
- package/dist/services/jwt.service.d.ts.map +1 -1
- package/dist/services/jwt.service.js +13 -1
- package/dist/services/jwt.service.js.map +1 -1
- package/dist/services/mail-queue.service.d.ts +24 -0
- package/dist/services/mail-queue.service.d.ts.map +1 -0
- package/dist/services/mail-queue.service.js +223 -0
- package/dist/services/mail-queue.service.js.map +1 -0
- package/dist/services/mikro.service.d.ts.map +1 -1
- package/dist/services/mikro.service.js +2 -0
- package/dist/services/mikro.service.js.map +1 -1
- package/dist/services/oauth-authorize.service.d.ts +3 -0
- package/dist/services/oauth-authorize.service.d.ts.map +1 -1
- package/dist/services/oauth-authorize.service.js +131 -101
- package/dist/services/oauth-authorize.service.js.map +1 -1
- package/dist/services/oauth-browser-security.service.d.ts +7 -0
- package/dist/services/oauth-browser-security.service.d.ts.map +1 -0
- package/dist/services/oauth-browser-security.service.js +67 -0
- package/dist/services/oauth-browser-security.service.js.map +1 -0
- package/dist/services/oauth-client.service.d.ts +24 -1
- package/dist/services/oauth-client.service.d.ts.map +1 -1
- package/dist/services/oauth-client.service.js +52 -13
- package/dist/services/oauth-client.service.js.map +1 -1
- package/dist/services/oauth-connect.service.d.ts +37 -3
- package/dist/services/oauth-connect.service.d.ts.map +1 -1
- package/dist/services/oauth-connect.service.js +236 -212
- package/dist/services/oauth-connect.service.js.map +1 -1
- package/dist/services/oauth-token.service.d.ts +6 -4
- package/dist/services/oauth-token.service.d.ts.map +1 -1
- package/dist/services/oauth-token.service.js +295 -209
- package/dist/services/oauth-token.service.js.map +1 -1
- package/dist/services/passkey.service.d.ts +26 -7
- package/dist/services/passkey.service.d.ts.map +1 -1
- package/dist/services/passkey.service.js +71 -42
- package/dist/services/passkey.service.js.map +1 -1
- package/dist/services/password-auth.service.d.ts +5 -1
- package/dist/services/password-auth.service.d.ts.map +1 -1
- package/dist/services/password-auth.service.js +79 -52
- package/dist/services/password-auth.service.js.map +1 -1
- package/dist/services/password-reset.service.d.ts.map +1 -1
- package/dist/services/password-reset.service.js +34 -15
- package/dist/services/password-reset.service.js.map +1 -1
- package/dist/services/security.service.d.ts +3 -1
- package/dist/services/security.service.d.ts.map +1 -1
- package/dist/services/security.service.js +11 -2
- package/dist/services/security.service.js.map +1 -1
- package/dist/services/terms-policy.service.d.ts +7 -0
- package/dist/services/terms-policy.service.d.ts.map +1 -0
- package/dist/services/terms-policy.service.js +26 -0
- package/dist/services/terms-policy.service.js.map +1 -0
- package/dist/services/totp.service.d.ts +4 -5
- package/dist/services/totp.service.d.ts.map +1 -1
- package/dist/services/totp.service.js +116 -86
- package/dist/services/totp.service.js.map +1 -1
- package/dist/services/user-security.service.d.ts +15 -0
- package/dist/services/user-security.service.d.ts.map +1 -0
- package/dist/services/user-security.service.js +57 -0
- package/dist/services/user-security.service.js.map +1 -0
- package/dist/services/user.service.d.ts +6 -4
- package/dist/services/user.service.d.ts.map +1 -1
- package/dist/services/user.service.js +143 -121
- package/dist/services/user.service.js.map +1 -1
- package/frontend/client/assets/{auth-layout-BedWIZ7L.js → auth-layout-sea6nww4.js} +1 -1
- package/frontend/client/assets/{auth-page-header-C6Z6Air-.js → auth-page-header-CV85AJLW.js} +1 -1
- package/frontend/client/assets/document-navigation-FZbb5ldf.js +1 -0
- package/frontend/client/assets/i18n-vfKY-byx.js +1 -0
- package/frontend/client/assets/{language-selector-CeLHnp1n.js → language-selector-Jso2cQNI.js} +1 -1
- package/frontend/client/assets/manifest-90703545.js +1 -0
- package/frontend/client/assets/{passkey-Bkqvhjwb.js → passkey-CJhT-7yO.js} +1 -1
- package/frontend/client/assets/{promise-CepnkPDw.js → promise-BP9iSIs-.js} +1 -1
- package/frontend/client/assets/{root-CM8lJbh5.js → root-BkavuloG.js} +1 -1
- package/frontend/client/assets/{route-Y1_yJKhW.js → route-B0udr9eT.js} +1 -1
- package/frontend/client/assets/{route-CdPHVY9W.js → route-B3Uv9aTa.js} +1 -1
- package/frontend/client/assets/{route-CF1S7UUy.js → route-B6hIfgxz.js} +1 -1
- package/frontend/client/assets/{route-BcXtt6V2.js → route-BEWds2g-.js} +1 -1
- package/frontend/client/assets/route-Bm9LsorH.js +1 -0
- package/frontend/client/assets/route-Btv5Y69z.js +1 -0
- package/frontend/client/assets/route-BvyDzziE.js +1 -0
- package/frontend/client/assets/route-C6kG9u7j.js +1 -0
- package/frontend/client/assets/{route-1rid8j-3.js → route-C7C3Z0vI.js} +1 -1
- package/frontend/client/assets/{route-DphbGk_G.js → route-CVyefM3x.js} +1 -1
- package/frontend/client/assets/{route-DZvCkU11.js → route-CXth2Wqz.js} +1 -1
- package/frontend/client/assets/route-D0IqOurp.js +1 -0
- package/frontend/client/assets/route-DHmYUnJR.js +1 -0
- package/frontend/client/assets/route-DTjZQ_Fl.js +1 -0
- package/frontend/client/assets/route-D_dTeu1b.js +1 -0
- package/frontend/client/assets/{route-DkoXJmYM.js → route-Dj8gVemC.js} +1 -1
- package/frontend/client/assets/{route-DrtpzI6u.js → route-DnhPlHOi.js} +1 -1
- package/frontend/client/assets/route-FY92YQhF.js +1 -0
- package/frontend/client/assets/{route-CKiun7AR.js → route-PcLpZHbT.js} +1 -1
- package/frontend/client/assets/{route-BwhQptmx.js → route-qpE7i22X.js} +1 -1
- package/frontend/client/assets/{route-HA9dma-h.js → route-wK89gmoS.js} +1 -1
- package/frontend/client/assets/security-error-message-CMsTsiWf.js +1 -0
- package/frontend/client/assets/{shell-header-bar-CjPlfRKT.js → shell-header-bar-DRx10tE5.js} +1 -1
- package/frontend/client/assets/{terms-BGJD_l_i.js → terms-Bu9Itxxs.js} +1 -1
- package/frontend/client/assets/{use-totp-setup-Cwuds2tR.js → use-totp-setup-CoOXWOcT.js} +1 -1
- package/frontend/server/assets/{hydration-B63_G0fd.js → hydration-DWMjpNav.js} +1 -1
- package/frontend/server/assets/{root-loader.server-DUIBlg_3.js → root-loader.server-B4OqoLGG.js} +3 -3
- package/frontend/server/assets/{route-runtime.server-DiqwWb8W.js → route-runtime.server-03_pjVDT.js} +1 -1
- package/frontend/server/assets/{session-BaAt7WMI.js → session-sJkmzJie.js} +36 -24
- package/frontend/server/index.js +239 -193
- package/package.json +1 -1
- package/frontend/client/assets/document-navigation-Cnp3Alf0.js +0 -1
- package/frontend/client/assets/i18n-Dzv9HKFm.js +0 -1
- package/frontend/client/assets/manifest-71025d92.js +0 -1
- package/frontend/client/assets/route-B4jDMFTW.js +0 -1
- package/frontend/client/assets/route-C-DecXPL.js +0 -1
- package/frontend/client/assets/route-CGTMMDiX.js +0 -1
- package/frontend/client/assets/route-CMJbXiDe.js +0 -1
- package/frontend/client/assets/route-DBpAiakW.js +0 -1
- package/frontend/client/assets/route-DS92RKs5.js +0 -1
- package/frontend/client/assets/route-Dc6G2aDX.js +0 -1
- package/frontend/client/assets/route-DqAmRSJE.js +0 -1
- package/frontend/client/assets/route-Ezzcj26w.js +0 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { raw } from '@mikro-orm/core';
|
|
2
|
+
import { OAuthGrantEntitySchema } from '../entities/oauth-grant.entity.js';
|
|
1
3
|
import { stringToBytes, toArrayBuffer, toBase64Url } from '../lib/base64url.js';
|
|
2
4
|
import { validatePKCE } from '../lib/pkce.js';
|
|
5
|
+
import { IssuaryError } from '../schemas/error.js';
|
|
3
6
|
import { e } from '../schemas/error.js';
|
|
7
|
+
import { lockOAuthClient } from './client-security.js';
|
|
4
8
|
/**
|
|
5
9
|
* OAuth Token Service
|
|
6
10
|
*
|
|
@@ -17,7 +21,6 @@ export class OAuthTokenService {
|
|
|
17
21
|
oauthClientService;
|
|
18
22
|
jwtService;
|
|
19
23
|
securityService;
|
|
20
|
-
refreshRotationLocks = new Map();
|
|
21
24
|
constructor(config, mikro, userService, oauthClientService, jwtService, securityService) {
|
|
22
25
|
this.config = config;
|
|
23
26
|
this.mikro = mikro;
|
|
@@ -42,69 +45,88 @@ export class OAuthTokenService {
|
|
|
42
45
|
async exchangeAuthorizationCode(params) {
|
|
43
46
|
const { code, redirectUri, clientId, codeVerifier } = params;
|
|
44
47
|
// 1. Look up client to get primary key (clientId in request is the business key)
|
|
45
|
-
const client = await this.oauthClientService.findByClientId(clientId);
|
|
46
|
-
// 2. Verify and consume the authorization code
|
|
47
|
-
// Authorization codes are single-use (RFC 6749 §4.1.2)
|
|
48
48
|
const codeHash = await this.securityService.hashOpaqueToken('oauth-code', code);
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
throw new e.RedirectUriMismatch.Error();
|
|
64
|
-
}
|
|
65
|
-
// 5. Validate S256 PKCE when the authorization request used PKCE.
|
|
66
|
-
// Public clients are required to use PKCE at the authorization endpoint;
|
|
67
|
-
// confidential clients may omit PKCE and rely on client authentication at
|
|
68
|
-
// the token endpoint, but any supplied challenge must still be valid S256.
|
|
69
|
-
if (!codeEntity.codeChallenge) {
|
|
70
|
-
if (await this.oauthClientService.isPublicClient(clientId)) {
|
|
71
|
-
throw new e.InvalidPKCEVerifier.Error();
|
|
49
|
+
const result = await this.mikro.em.transactional(async () => {
|
|
50
|
+
const client = await this.oauthClientService.lockAuthenticatedClient(params.authentication, clientId, 'authorization_code');
|
|
51
|
+
const codeEntity = await this.mikro.oauthCode.findOne({ client: client.id, codeHash }, { refresh: true });
|
|
52
|
+
if (!codeEntity ||
|
|
53
|
+
codeEntity.client_epoch !== client.tokenEpoch ||
|
|
54
|
+
(!codeEntity.consumedAt && codeEntity.expiredAt <= new Date()))
|
|
55
|
+
throw new e.InvalidAuthorizationCode.Error();
|
|
56
|
+
this.oauthClientService.validateScopes(client, codeEntity.scope);
|
|
57
|
+
// 3. Populate user relation
|
|
58
|
+
await this.mikro.em.populate(codeEntity, ['user']);
|
|
59
|
+
// 4. Validate redirect_uri matches (RFC 6749 §4.1.3)
|
|
60
|
+
// This prevents authorization code interception attacks
|
|
61
|
+
if (codeEntity.redirectUri !== redirectUri) {
|
|
62
|
+
throw new e.RedirectUriMismatch.Error();
|
|
72
63
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
64
|
+
this.oauthClientService.validateRedirectUri(client, redirectUri);
|
|
65
|
+
// 5. Validate S256 PKCE when the authorization request used PKCE.
|
|
66
|
+
// Public clients are required to use PKCE at the authorization endpoint;
|
|
67
|
+
// confidential clients may omit PKCE and rely on client authentication at
|
|
68
|
+
// the token endpoint, but any supplied challenge must still be valid S256.
|
|
69
|
+
if (!codeEntity.codeChallenge) {
|
|
70
|
+
if (await this.oauthClientService.isPublicClient(clientId)) {
|
|
71
|
+
throw new e.InvalidPKCEVerifier.Error();
|
|
72
|
+
}
|
|
77
73
|
}
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
else {
|
|
75
|
+
if (codeEntity.codeChallengeMethod !== 'S256') {
|
|
76
|
+
throw new e.InvalidPKCEVerifier.Error();
|
|
77
|
+
}
|
|
78
|
+
if (!codeVerifier) {
|
|
79
|
+
throw new e.MissingCodeVerifier.Error();
|
|
80
|
+
}
|
|
81
|
+
const isPKCEValid = await validatePKCE(codeVerifier, codeEntity.codeChallenge, codeEntity.codeChallengeMethod);
|
|
82
|
+
if (!isPKCEValid) {
|
|
83
|
+
throw new e.InvalidPKCEVerifier.Error();
|
|
84
|
+
}
|
|
80
85
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
86
|
+
// 6. Get user data from relation (load via Ref)
|
|
87
|
+
const user = await codeEntity.user.load();
|
|
88
|
+
if (!user) {
|
|
89
|
+
throw new e.UserNotFound.Error();
|
|
84
90
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
91
|
+
if (user.deleted_at || codeEntity.user_epoch !== user.token_epoch)
|
|
92
|
+
throw new e.InvalidAuthorizationCode.Error();
|
|
93
|
+
if (codeEntity.consumedAt) {
|
|
94
|
+
if (codeEntity.grant_id)
|
|
95
|
+
await this.mikro.em.nativeUpdate(OAuthGrantEntitySchema, { id: codeEntity.grant_id, client_id: client.clientId }, { revoked_at: new Date() });
|
|
96
|
+
return new e.InvalidAuthorizationCode.Error();
|
|
97
|
+
}
|
|
98
|
+
const consumed = await this.mikro.oauthCode.consumeAuthorizationCode({
|
|
99
|
+
clientId: client.id,
|
|
100
|
+
codeHash,
|
|
101
|
+
consumedAt: new Date(),
|
|
102
|
+
});
|
|
103
|
+
if (!consumed)
|
|
104
|
+
return new e.InvalidAuthorizationCode.Error();
|
|
105
|
+
const grantId = crypto.randomUUID();
|
|
106
|
+
await this.mikro.oauthCode.nativeUpdate({ id: codeEntity.id }, { grant_id: grantId });
|
|
107
|
+
// 7. Build token response
|
|
108
|
+
return this.buildTokenResponse({
|
|
109
|
+
grantId,
|
|
110
|
+
userSub: user.sub,
|
|
111
|
+
userEpoch: user.token_epoch ?? undefined,
|
|
112
|
+
userEmail: user.email,
|
|
113
|
+
userEmailVerified: user.email_verified,
|
|
114
|
+
clientId: client.clientId,
|
|
115
|
+
clientEpoch: client.tokenEpoch,
|
|
116
|
+
scope: codeEntity.scope,
|
|
117
|
+
issueRefreshToken: client.grantTypes.includes('refresh_token') &&
|
|
118
|
+
codeEntity.scope.includes('offline_access'),
|
|
119
|
+
nonce: codeEntity.nonce,
|
|
120
|
+
// Pass OIDC authentication metadata from the authorization code
|
|
121
|
+
// Only include when defined and non-null (exactOptionalPropertyTypes)
|
|
122
|
+
...(codeEntity.authTime != null && {
|
|
123
|
+
authTime: codeEntity.authTime,
|
|
124
|
+
}),
|
|
125
|
+
});
|
|
107
126
|
});
|
|
127
|
+
if (result instanceof IssuaryError)
|
|
128
|
+
throw result;
|
|
129
|
+
return result;
|
|
108
130
|
}
|
|
109
131
|
/**
|
|
110
132
|
* Refresh access token using refresh token
|
|
@@ -123,83 +145,114 @@ export class OAuthTokenService {
|
|
|
123
145
|
* @throws {ClientIdMismatch} - Client ID doesn't match original token request
|
|
124
146
|
*/
|
|
125
147
|
async refreshAccessToken(params) {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
148
|
+
const decoded = await this.jwtService.verifyRefreshTokenForReuseDetection(params.refreshToken);
|
|
149
|
+
if (decoded.client_id !== params.clientId)
|
|
150
|
+
throw new e.ClientIdMismatch.Error();
|
|
151
|
+
if (!decoded.grant_id || !decoded.jti)
|
|
152
|
+
throw new e.InvalidRefreshToken.Error();
|
|
153
|
+
const grantId = decoded.grant_id;
|
|
154
|
+
const result = await this.mikro.em.transactional(async () => {
|
|
155
|
+
await this.oauthClientService.lockAuthenticatedClient(params.authentication, params.clientId, 'refresh_token');
|
|
156
|
+
const locked = await this.mikro.em.nativeUpdate(OAuthGrantEntitySchema, { id: grantId, client_id: params.clientId, user_sub: decoded.sub }, { revision: raw('revision + 1') });
|
|
157
|
+
if (locked !== 1)
|
|
158
|
+
throw new e.InvalidRefreshToken.Error();
|
|
159
|
+
const grant = await this.mikro.em.findOneOrFail(OAuthGrantEntitySchema, { id: grantId }, { refresh: true });
|
|
160
|
+
if (grant.revoked_at || grant.expires_at <= new Date())
|
|
161
|
+
throw new e.InvalidRefreshToken.Error();
|
|
162
|
+
if (grant.current_refresh_jti !== decoded.jti) {
|
|
163
|
+
await this.mikro.em.nativeUpdate(OAuthGrantEntitySchema, { id: grant.id }, { revoked_at: new Date() });
|
|
164
|
+
return new e.InvalidRefreshToken.Error();
|
|
165
|
+
}
|
|
166
|
+
return this.refreshAccessTokenLocked(params);
|
|
167
|
+
});
|
|
168
|
+
if (result instanceof IssuaryError)
|
|
169
|
+
throw result;
|
|
170
|
+
return result;
|
|
134
171
|
}
|
|
135
172
|
async issueClientCredentialsToken(params) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
173
|
+
return this.mikro.em.transactional(async () => {
|
|
174
|
+
const client = await this.oauthClientService.lockAuthenticatedClient(params.authentication, params.clientId, 'client_credentials');
|
|
175
|
+
if (params.authentication.kind !== 'confidential')
|
|
176
|
+
throw new e.InvalidClientCredentials.Error();
|
|
177
|
+
this.oauthClientService.validateScopes(client, params.scope);
|
|
178
|
+
const scopeString = params.scope.join(' ');
|
|
179
|
+
const accessToken = await this.jwtService.signAccessToken({
|
|
180
|
+
typ: 'access_token',
|
|
181
|
+
sub: params.clientId,
|
|
182
|
+
client_id: params.clientId,
|
|
183
|
+
grant_type: 'client_credentials',
|
|
184
|
+
scope: scopeString,
|
|
185
|
+
aud: this.config.server.public_origin,
|
|
186
|
+
...(client.tokenEpoch && { client_epoch: client.tokenEpoch }),
|
|
187
|
+
});
|
|
188
|
+
return {
|
|
189
|
+
access_token: accessToken,
|
|
190
|
+
token_type: 'Bearer',
|
|
191
|
+
expires_in: this.config.tokens.access_token_ttl,
|
|
192
|
+
scope: scopeString,
|
|
193
|
+
};
|
|
147
194
|
});
|
|
148
|
-
return {
|
|
149
|
-
access_token: accessToken,
|
|
150
|
-
token_type: 'Bearer',
|
|
151
|
-
expires_in: this.config.tokens.access_token_ttl,
|
|
152
|
-
scope: scopeString,
|
|
153
|
-
};
|
|
154
195
|
}
|
|
155
196
|
async exchangeDeviceCode(params) {
|
|
156
|
-
const client = await this.oauthClientService.findByClientId(params.clientId);
|
|
157
197
|
const deviceCodeHash = await this.securityService.hashOpaqueToken('oauth-device-code', params.deviceCode);
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
throw new e.ExpiredToken.Error();
|
|
164
|
-
}
|
|
165
|
-
if (deviceCode.deniedAt) {
|
|
166
|
-
throw new e.AccessDenied.Error();
|
|
167
|
-
}
|
|
168
|
-
if (!deviceCode.authorizedUser) {
|
|
169
|
-
const pollResult = await this.mikro.oauthDeviceCode.recordPendingPoll({
|
|
170
|
-
id: deviceCode.id,
|
|
171
|
-
polledAt: new Date(),
|
|
172
|
-
});
|
|
173
|
-
if (pollResult === 'slow_down') {
|
|
174
|
-
throw new e.SlowDown.Error();
|
|
198
|
+
const result = await this.mikro.em.transactional(async () => {
|
|
199
|
+
const client = await this.oauthClientService.lockAuthenticatedClient(params.authentication, params.clientId, 'urn:ietf:params:oauth:grant-type:device_code');
|
|
200
|
+
const deviceCode = await this.mikro.oauthDeviceCode.findByClientAndDeviceCodeHash(client.id, deviceCodeHash);
|
|
201
|
+
if (!deviceCode || deviceCode.client_epoch !== client.tokenEpoch) {
|
|
202
|
+
throw new e.InvalidDeviceCode.Error();
|
|
175
203
|
}
|
|
176
|
-
|
|
204
|
+
this.oauthClientService.validateScopes(client, deviceCode.scope);
|
|
205
|
+
if (deviceCode.expiresAt <= new Date()) {
|
|
206
|
+
throw new e.ExpiredToken.Error();
|
|
207
|
+
}
|
|
208
|
+
if (deviceCode.deniedAt) {
|
|
209
|
+
throw new e.AccessDenied.Error();
|
|
210
|
+
}
|
|
211
|
+
if (!deviceCode.authorizedUser) {
|
|
212
|
+
const pollResult = await this.mikro.oauthDeviceCode.recordPendingPoll({
|
|
213
|
+
id: deviceCode.id,
|
|
214
|
+
polledAt: new Date(),
|
|
215
|
+
});
|
|
216
|
+
if (pollResult === 'slow_down') {
|
|
217
|
+
return new e.SlowDown.Error();
|
|
218
|
+
}
|
|
219
|
+
if (pollResult === 'authorization_pending') {
|
|
220
|
+
return new e.AuthorizationPending.Error();
|
|
221
|
+
}
|
|
222
|
+
throw new e.InvalidDeviceCode.Error();
|
|
223
|
+
}
|
|
224
|
+
const consumedAt = new Date();
|
|
225
|
+
const consumed = await this.mikro.oauthDeviceCode.consumeAuthorizedDeviceCode(deviceCode.id, consumedAt);
|
|
226
|
+
if (!consumed) {
|
|
227
|
+
throw new e.InvalidDeviceCode.Error();
|
|
228
|
+
}
|
|
229
|
+
deviceCode.consumedAt = consumedAt;
|
|
230
|
+
await this.mikro.em.populate(deviceCode, ['authorizedUser']);
|
|
231
|
+
const user = deviceCode.authorizedUser;
|
|
232
|
+
if (!user) {
|
|
177
233
|
throw new e.AuthorizationPending.Error();
|
|
178
234
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
clientId: client.clientId,
|
|
197
|
-
clientEpoch: client.tokenEpoch,
|
|
198
|
-
scope: deviceCode.scope,
|
|
199
|
-
issueRefreshToken: client.grantTypes.includes('refresh_token') &&
|
|
200
|
-
deviceCode.scope.includes('offline_access'),
|
|
201
|
-
authTime: Math.floor((deviceCode.authorizedAt?.getTime() ?? Date.now()) / 1000),
|
|
235
|
+
if (user.deleted_at || deviceCode.user_epoch !== user.token_epoch)
|
|
236
|
+
throw new e.InvalidDeviceCode.Error();
|
|
237
|
+
const grantId = crypto.randomUUID();
|
|
238
|
+
await this.mikro.oauthDeviceCode.nativeUpdate({ id: deviceCode.id }, { grant_id: grantId });
|
|
239
|
+
return this.buildTokenResponse({
|
|
240
|
+
grantId,
|
|
241
|
+
userSub: user.sub,
|
|
242
|
+
userEpoch: user.token_epoch ?? undefined,
|
|
243
|
+
userEmail: user.email,
|
|
244
|
+
userEmailVerified: user.email_verified,
|
|
245
|
+
clientId: client.clientId,
|
|
246
|
+
clientEpoch: client.tokenEpoch,
|
|
247
|
+
scope: deviceCode.scope,
|
|
248
|
+
issueRefreshToken: client.grantTypes.includes('refresh_token') &&
|
|
249
|
+
deviceCode.scope.includes('offline_access'),
|
|
250
|
+
authTime: Math.floor((deviceCode.authorizedAt?.getTime() ?? Date.now()) / 1000),
|
|
251
|
+
});
|
|
202
252
|
});
|
|
253
|
+
if (result instanceof Error)
|
|
254
|
+
throw result;
|
|
255
|
+
return result;
|
|
203
256
|
}
|
|
204
257
|
async refreshAccessTokenLocked(params) {
|
|
205
258
|
const { refreshToken, clientId } = params;
|
|
@@ -224,36 +277,36 @@ export class OAuthTokenService {
|
|
|
224
277
|
const client = await this.oauthClientService.findByClientId(clientId);
|
|
225
278
|
const refreshTokenScopes = refreshPayload.scope.split(' ');
|
|
226
279
|
const requestedScopes = params.scope ?? refreshTokenScopes;
|
|
280
|
+
this.oauthClientService.validateScopes(client, requestedScopes);
|
|
227
281
|
const invalidScopes = requestedScopes.filter((scope) => !refreshTokenScopes.includes(scope));
|
|
228
282
|
if (invalidScopes.length > 0) {
|
|
229
283
|
throw new e.InvalidScope.Error({ invalidScopes });
|
|
230
284
|
}
|
|
231
|
-
|
|
232
|
-
// This is a security best practice per OAuth 2.0 Security BCP §4.14.2
|
|
233
|
-
// If an attacker tries to use a stolen refresh token after the legitimate
|
|
234
|
-
// user has already used it, the token will be rejected as revoked.
|
|
235
|
-
if (!refreshPayload.jti || !refreshPayload.exp) {
|
|
285
|
+
if (!refreshPayload.jti || !refreshPayload.exp || !refreshPayload.grant_id)
|
|
236
286
|
throw new e.InvalidRefreshToken.Error();
|
|
237
|
-
|
|
238
|
-
|
|
287
|
+
const consumed = await this.mikro.em.nativeUpdate(OAuthGrantEntitySchema, {
|
|
288
|
+
id: refreshPayload.grant_id,
|
|
289
|
+
current_refresh_jti: refreshPayload.jti,
|
|
290
|
+
revoked_at: null,
|
|
291
|
+
}, { current_refresh_jti: null });
|
|
292
|
+
if (consumed !== 1)
|
|
293
|
+
throw new e.InvalidRefreshToken.Error();
|
|
294
|
+
await this.mikro.revokedToken.revokeTokenOnce({
|
|
239
295
|
jti: refreshPayload.jti,
|
|
240
296
|
token_type: 'refresh_token',
|
|
241
|
-
clientId: client.id,
|
|
297
|
+
clientId: client.id,
|
|
242
298
|
userSub: userData.sub,
|
|
243
299
|
expires_at: new Date(refreshPayload.exp * 1000),
|
|
244
300
|
});
|
|
245
|
-
if (!didRevokeRefreshToken) {
|
|
246
|
-
await this.revokeRefreshTokenFamily(refreshPayload, client.id);
|
|
247
|
-
throw new e.InvalidRefreshToken.Error();
|
|
248
|
-
}
|
|
249
301
|
// 6. Build token response with new access and refresh tokens
|
|
250
302
|
// (no nonce in refresh flow)
|
|
251
303
|
return this.buildTokenResponse({
|
|
252
304
|
userSub: userData.sub,
|
|
305
|
+
userEpoch: refreshPayload.user_epoch,
|
|
253
306
|
userEmail: userData.email,
|
|
254
307
|
userEmailVerified: userData.email_verified,
|
|
255
308
|
clientId: client.clientId,
|
|
256
|
-
clientEpoch:
|
|
309
|
+
clientEpoch: refreshPayload.client_epoch ?? null,
|
|
257
310
|
scope: requestedScopes,
|
|
258
311
|
issueRefreshToken: true,
|
|
259
312
|
grantId: refreshPayload.grant_id,
|
|
@@ -359,81 +412,85 @@ export class OAuthTokenService {
|
|
|
359
412
|
* @param tokenTypeHint - Hint about token type (access_token or refresh_token)
|
|
360
413
|
* @returns void - Always succeeds per RFC 7009 §2.1
|
|
361
414
|
*/
|
|
362
|
-
async revokeToken(token, tokenTypeHint, requestingClientId) {
|
|
415
|
+
async revokeToken(token, tokenTypeHint, requestingClientId, authentication) {
|
|
363
416
|
const verification = await this.verifyTokenForRevocation(token, tokenTypeHint);
|
|
364
417
|
if (!verification) {
|
|
365
418
|
return;
|
|
366
419
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
// Note: clientId from token is the business key, we need the entity's primary key
|
|
383
|
-
const userEntity = await this.mikro.user.findOne({ sub: payload.sub });
|
|
384
|
-
const clientEntity = await this.mikro.oauthClient.findOne({
|
|
385
|
-
clientId: payload.client_id,
|
|
386
|
-
});
|
|
387
|
-
if (!clientEntity) {
|
|
388
|
-
// Client no longer exists, but we still return success per RFC 7009.
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
const isClientCredentialsAccessToken = tokenType === 'access_token' &&
|
|
392
|
-
'grant_type' in payload &&
|
|
393
|
-
payload.grant_type === 'client_credentials';
|
|
394
|
-
if (!userEntity && !isClientCredentialsAccessToken) {
|
|
395
|
-
// User no longer exists, but we still return success per RFC 7009.
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
// Revoke the token (using primary keys for FK references)
|
|
399
|
-
await this.mikro.revokedToken.revokeToken({
|
|
400
|
-
jti,
|
|
401
|
-
token_type: tokenType,
|
|
402
|
-
clientId: clientEntity.id, // Use entity's primary key
|
|
403
|
-
...(userEntity !== null && { userSub: userEntity.sub }),
|
|
404
|
-
expires_at: expiresAt,
|
|
405
|
-
});
|
|
406
|
-
if (tokenType === 'refresh_token') {
|
|
407
|
-
await this.revokeRefreshTokenFamily(payload, clientEntity.id);
|
|
408
|
-
}
|
|
409
|
-
// RFC 7009 §2.1: "If the particular token is a refresh token and the
|
|
410
|
-
// authorization server supports the revocation of access tokens, then
|
|
411
|
-
// the authorization server SHOULD also invalidate all access tokens
|
|
412
|
-
// based on the same authorization grant."
|
|
413
|
-
//
|
|
414
|
-
// Since we can't enumerate all access tokens issued for this refresh token,
|
|
415
|
-
// the revocation check happens at token verification time via jti lookup.
|
|
416
|
-
// Access tokens will be rejected when their jti is in the revoked_tokens table.
|
|
417
|
-
}
|
|
418
|
-
async withRefreshTokenRotationLock(refreshTokenJti, operation) {
|
|
419
|
-
const previousLock = this.refreshRotationLocks.get(refreshTokenJti);
|
|
420
|
-
let releaseLock = () => { };
|
|
421
|
-
const currentLock = new Promise((resolve) => {
|
|
422
|
-
releaseLock = resolve;
|
|
423
|
-
});
|
|
424
|
-
this.refreshRotationLocks.set(refreshTokenJti, currentLock);
|
|
425
|
-
if (previousLock) {
|
|
426
|
-
await previousLock;
|
|
427
|
-
}
|
|
428
|
-
try {
|
|
429
|
-
return await operation();
|
|
430
|
-
}
|
|
431
|
-
finally {
|
|
432
|
-
releaseLock();
|
|
433
|
-
if (this.refreshRotationLocks.get(refreshTokenJti) === currentLock) {
|
|
434
|
-
this.refreshRotationLocks.delete(refreshTokenJti);
|
|
420
|
+
return this.mikro.em.transactional(async () => {
|
|
421
|
+
if (authentication)
|
|
422
|
+
await this.oauthClientService.lockAuthenticatedClient(authentication, requestingClientId ?? authentication.clientId);
|
|
423
|
+
const owner = await this.mikro.oauthClient.findOne({
|
|
424
|
+
clientId: verification.payload.client_id,
|
|
425
|
+
});
|
|
426
|
+
if (!owner)
|
|
427
|
+
return;
|
|
428
|
+
await lockOAuthClient(this.mikro.em, owner.id);
|
|
429
|
+
const fresh = await this.verifyTokenForRevocation(token, tokenTypeHint);
|
|
430
|
+
if (!fresh)
|
|
431
|
+
return;
|
|
432
|
+
const { payload, tokenType } = fresh;
|
|
433
|
+
if (requestingClientId && payload.client_id !== requestingClientId) {
|
|
434
|
+
return;
|
|
435
435
|
}
|
|
436
|
-
|
|
436
|
+
const { jti, exp } = payload;
|
|
437
|
+
if (!jti || !exp) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const expiresAt = new Date(exp * 1000);
|
|
441
|
+
// Check if already revoked
|
|
442
|
+
const isAlreadyRevoked = await this.mikro.revokedToken.isRevoked(jti);
|
|
443
|
+
if (isAlreadyRevoked && tokenType !== 'refresh_token') {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
// Look up user and client entities to get primary keys
|
|
447
|
+
// Note: clientId from token is the business key, we need the entity's primary key
|
|
448
|
+
const userEntity = await this.mikro.user.findOne({ sub: payload.sub });
|
|
449
|
+
const clientEntity = await this.mikro.oauthClient.findOne({
|
|
450
|
+
clientId: payload.client_id,
|
|
451
|
+
});
|
|
452
|
+
if (!clientEntity) {
|
|
453
|
+
// Client no longer exists, but we still return success per RFC 7009.
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const isClientCredentialsAccessToken = tokenType === 'access_token' &&
|
|
457
|
+
'grant_type' in payload &&
|
|
458
|
+
payload.grant_type === 'client_credentials';
|
|
459
|
+
if (!userEntity && !isClientCredentialsAccessToken) {
|
|
460
|
+
// User no longer exists, but we still return success per RFC 7009.
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (tokenType === 'refresh_token') {
|
|
464
|
+
if (!payload.grant_id)
|
|
465
|
+
return;
|
|
466
|
+
const locked = await this.mikro.em.nativeUpdate(OAuthGrantEntitySchema, {
|
|
467
|
+
id: payload.grant_id,
|
|
468
|
+
client_id: payload.client_id,
|
|
469
|
+
user_sub: payload.sub,
|
|
470
|
+
}, { revision: raw('revision + 1') });
|
|
471
|
+
if (locked !== 1)
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
// Revoke the token (using primary keys for FK references)
|
|
475
|
+
await this.mikro.revokedToken.revokeToken({
|
|
476
|
+
jti,
|
|
477
|
+
token_type: tokenType,
|
|
478
|
+
clientId: clientEntity.id, // Use entity's primary key
|
|
479
|
+
...(userEntity !== null && { userSub: userEntity.sub }),
|
|
480
|
+
expires_at: expiresAt,
|
|
481
|
+
});
|
|
482
|
+
if (tokenType === 'refresh_token') {
|
|
483
|
+
await this.revokeRefreshTokenFamily(payload, clientEntity.id);
|
|
484
|
+
}
|
|
485
|
+
// RFC 7009 §2.1: "If the particular token is a refresh token and the
|
|
486
|
+
// authorization server supports the revocation of access tokens, then
|
|
487
|
+
// the authorization server SHOULD also invalidate all access tokens
|
|
488
|
+
// based on the same authorization grant."
|
|
489
|
+
//
|
|
490
|
+
// Since we can't enumerate all access tokens issued for this refresh token,
|
|
491
|
+
// the revocation check happens at token verification time via jti lookup.
|
|
492
|
+
// Access tokens will be rejected when their jti is in the revoked_tokens table.
|
|
493
|
+
});
|
|
437
494
|
}
|
|
438
495
|
async revokeRefreshTokenFamilyIfReused(refreshToken, clientId) {
|
|
439
496
|
try {
|
|
@@ -458,6 +515,7 @@ export class OAuthTokenService {
|
|
|
458
515
|
}
|
|
459
516
|
const tokenExpiresAt = payload.exp * 1000;
|
|
460
517
|
const familyExpiresAt = Date.now() + this.config.tokens.refresh_token_ttl * 1000;
|
|
518
|
+
await this.mikro.em.nativeUpdate(OAuthGrantEntitySchema, { id: payload.grant_id, client_id: payload.client_id }, { revoked_at: new Date() });
|
|
461
519
|
await this.mikro.revokedToken.revokeGrant({
|
|
462
520
|
grantId: payload.grant_id,
|
|
463
521
|
clientId: clientEntityId,
|
|
@@ -471,7 +529,7 @@ export class OAuthTokenService {
|
|
|
471
529
|
tokenType: 'access_token',
|
|
472
530
|
});
|
|
473
531
|
const verifyRefreshToken = async () => ({
|
|
474
|
-
payload: await this.jwtService.
|
|
532
|
+
payload: await this.jwtService.verifyRefreshTokenForReuseDetection(token),
|
|
475
533
|
tokenType: 'refresh_token',
|
|
476
534
|
});
|
|
477
535
|
const attempts = tokenTypeHint === 'refresh_token'
|
|
@@ -518,6 +576,7 @@ export class OAuthTokenService {
|
|
|
518
576
|
const accessToken = await this.jwtService.signAccessToken({
|
|
519
577
|
typ: 'access_token',
|
|
520
578
|
sub: userSub,
|
|
579
|
+
user_epoch: params.userEpoch,
|
|
521
580
|
client_id: clientId,
|
|
522
581
|
scope: scopeString,
|
|
523
582
|
aud: this.config.server.public_origin,
|
|
@@ -534,6 +593,7 @@ export class OAuthTokenService {
|
|
|
534
593
|
response.refresh_token = await this.jwtService.signRefreshToken({
|
|
535
594
|
typ: 'refresh_token',
|
|
536
595
|
sub: userSub,
|
|
596
|
+
user_epoch: params.userEpoch,
|
|
537
597
|
client_id: clientId,
|
|
538
598
|
scope: scopeString,
|
|
539
599
|
grant_id: grantId,
|
|
@@ -566,6 +626,32 @@ export class OAuthTokenService {
|
|
|
566
626
|
}
|
|
567
627
|
response.id_token = await this.jwtService.signIdToken(idTokenPayload);
|
|
568
628
|
}
|
|
629
|
+
const refreshJti = response.refresh_token
|
|
630
|
+
? this.jwtService.decodeToken(response.refresh_token)?.jti
|
|
631
|
+
: undefined;
|
|
632
|
+
const expiresAt = new Date(Date.now() +
|
|
633
|
+
Math.max(this.config.tokens.access_token_ttl, issueRefreshToken ? this.config.tokens.refresh_token_ttl : 0) *
|
|
634
|
+
1000);
|
|
635
|
+
const existing = await this.mikro.em.findOne(OAuthGrantEntitySchema, { id: grantId }, { refresh: true });
|
|
636
|
+
if (existing) {
|
|
637
|
+
if (existing.revoked_at)
|
|
638
|
+
throw new e.InvalidRefreshToken.Error();
|
|
639
|
+
await this.mikro.em.nativeUpdate(OAuthGrantEntitySchema, { id: grantId, revoked_at: null }, {
|
|
640
|
+
current_refresh_jti: typeof refreshJti === 'string' ? refreshJti : null,
|
|
641
|
+
expires_at: new Date(Math.max(existing.expires_at.getTime(), expiresAt.getTime())),
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
await this.mikro.em.insert(OAuthGrantEntitySchema, {
|
|
646
|
+
id: grantId,
|
|
647
|
+
user_sub: userSub,
|
|
648
|
+
client_id: clientId,
|
|
649
|
+
current_refresh_jti: typeof refreshJti === 'string' ? refreshJti : null,
|
|
650
|
+
expires_at: expiresAt,
|
|
651
|
+
revoked_at: null,
|
|
652
|
+
revision: 0,
|
|
653
|
+
});
|
|
654
|
+
}
|
|
569
655
|
return response;
|
|
570
656
|
}
|
|
571
657
|
}
|