@techelusive/nest-auth 0.0.0-stage → 0.2.0
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/CHANGELOG.md +64 -0
- package/LICENSE +21 -0
- package/README.md +204 -2
- package/api/openapi.json +4399 -0
- package/api/techelusive-authentication.postman_collection.json +3178 -0
- package/dist/auth-options.d.ts +156 -0
- package/dist/auth-options.js +3 -0
- package/dist/auth-options.js.map +1 -0
- package/dist/auth-options.validation.d.ts +9 -0
- package/dist/auth-options.validation.js +74 -0
- package/dist/auth-options.validation.js.map +1 -0
- package/dist/auth.module.d.ts +38 -0
- package/dist/auth.module.js +173 -0
- package/dist/auth.module.js.map +1 -0
- package/dist/common/auth-error.d.ts +16 -0
- package/dist/common/auth-error.js +23 -0
- package/dist/common/auth-error.js.map +1 -0
- package/dist/common/auth-exception.filter.d.ts +24 -0
- package/dist/common/auth-exception.filter.js +66 -0
- package/dist/common/auth-exception.filter.js.map +1 -0
- package/dist/common/crypto/jwt.d.ts +89 -0
- package/dist/common/crypto/jwt.js +144 -0
- package/dist/common/crypto/jwt.js.map +1 -0
- package/dist/common/crypto/random-secrets.d.ts +78 -0
- package/dist/common/crypto/random-secrets.js +113 -0
- package/dist/common/crypto/random-secrets.js.map +1 -0
- package/dist/common/crypto/remote-public-keys.d.ts +49 -0
- package/dist/common/crypto/remote-public-keys.js +105 -0
- package/dist/common/crypto/remote-public-keys.js.map +1 -0
- package/dist/common/crypto/totp.d.ts +68 -0
- package/dist/common/crypto/totp.js +148 -0
- package/dist/common/crypto/totp.js.map +1 -0
- package/dist/common/encryption.service.d.ts +36 -0
- package/dist/common/encryption.service.js +111 -0
- package/dist/common/encryption.service.js.map +1 -0
- package/dist/common/identifiers.d.ts +48 -0
- package/dist/common/identifiers.js +89 -0
- package/dist/common/identifiers.js.map +1 -0
- package/dist/common/network-safety.d.ts +19 -0
- package/dist/common/network-safety.js +72 -0
- package/dist/common/network-safety.js.map +1 -0
- package/dist/common/rate-limiter.service.d.ts +63 -0
- package/dist/common/rate-limiter.service.js +158 -0
- package/dist/common/rate-limiter.service.js.map +1 -0
- package/dist/common/refresh-token-cookie.d.ts +78 -0
- package/dist/common/refresh-token-cookie.js +185 -0
- package/dist/common/refresh-token-cookie.js.map +1 -0
- package/dist/common/request-context.d.ts +14 -0
- package/dist/common/request-context.js +22 -0
- package/dist/common/request-context.js.map +1 -0
- package/dist/common/strict-validation.pipe.d.ts +8 -0
- package/dist/common/strict-validation.pipe.js +16 -0
- package/dist/common/strict-validation.pipe.js.map +1 -0
- package/dist/common/time.d.ts +15 -0
- package/dist/common/time.js +34 -0
- package/dist/common/time.js.map +1 -0
- package/dist/constants.d.ts +1326 -0
- package/dist/constants.js +1142 -0
- package/dist/constants.js.map +1 -0
- package/dist/database/create-auth-tables.migration.d.ts +26 -0
- package/dist/database/create-auth-tables.migration.js +291 -0
- package/dist/database/create-auth-tables.migration.js.map +1 -0
- package/dist/database/email-change-and-retention.migration.d.ts +25 -0
- package/dist/database/email-change-and-retention.migration.js +54 -0
- package/dist/database/email-change-and-retention.migration.js.map +1 -0
- package/dist/database/entities/api-key.entity.d.ts +17 -0
- package/dist/database/entities/api-key.entity.js +80 -0
- package/dist/database/entities/api-key.entity.js.map +1 -0
- package/dist/database/entities/audit-log.entity.d.ts +13 -0
- package/dist/database/entities/audit-log.entity.js +64 -0
- package/dist/database/entities/audit-log.entity.js.map +1 -0
- package/dist/database/entities/backup-code.entity.d.ts +9 -0
- package/dist/database/entities/backup-code.entity.js +52 -0
- package/dist/database/entities/backup-code.entity.js.map +1 -0
- package/dist/database/entities/index.d.ts +43 -0
- package/dist/database/entities/index.js +82 -0
- package/dist/database/entities/index.js.map +1 -0
- package/dist/database/entities/json-object.d.ts +12 -0
- package/dist/database/entities/json-object.js +3 -0
- package/dist/database/entities/json-object.js.map +1 -0
- package/dist/database/entities/link-token.entity.d.ts +15 -0
- package/dist/database/entities/link-token.entity.js +64 -0
- package/dist/database/entities/link-token.entity.js.map +1 -0
- package/dist/database/entities/one-time-code.entity.d.ts +16 -0
- package/dist/database/entities/one-time-code.entity.js +69 -0
- package/dist/database/entities/one-time-code.entity.js.map +1 -0
- package/dist/database/entities/organization-invitation.entity.d.ts +14 -0
- package/dist/database/entities/organization-invitation.entity.js +73 -0
- package/dist/database/entities/organization-invitation.entity.js.map +1 -0
- package/dist/database/entities/organization-member.entity.d.ts +9 -0
- package/dist/database/entities/organization-member.entity.js +48 -0
- package/dist/database/entities/organization-member.entity.js.map +1 -0
- package/dist/database/entities/organization.entity.d.ts +12 -0
- package/dist/database/entities/organization.entity.js +58 -0
- package/dist/database/entities/organization.entity.js.map +1 -0
- package/dist/database/entities/passkey.entity.d.ts +19 -0
- package/dist/database/entities/passkey.entity.js +78 -0
- package/dist/database/entities/passkey.entity.js.map +1 -0
- package/dist/database/entities/password.entity.d.ts +6 -0
- package/dist/database/entities/password.entity.js +36 -0
- package/dist/database/entities/password.entity.js.map +1 -0
- package/dist/database/entities/refresh-token.entity.d.ts +9 -0
- package/dist/database/entities/refresh-token.entity.js +53 -0
- package/dist/database/entities/refresh-token.entity.js.map +1 -0
- package/dist/database/entities/session.entity.d.ts +20 -0
- package/dist/database/entities/session.entity.js +87 -0
- package/dist/database/entities/session.entity.js.map +1 -0
- package/dist/database/entities/settings.entity.d.ts +7 -0
- package/dist/database/entities/settings.entity.js +41 -0
- package/dist/database/entities/settings.entity.js.map +1 -0
- package/dist/database/entities/sign-in-challenge.entity.d.ts +23 -0
- package/dist/database/entities/sign-in-challenge.entity.js +80 -0
- package/dist/database/entities/sign-in-challenge.entity.js.map +1 -0
- package/dist/database/entities/signing-key.entity.d.ts +11 -0
- package/dist/database/entities/signing-key.entity.js +54 -0
- package/dist/database/entities/signing-key.entity.js.map +1 -0
- package/dist/database/entities/social-account.entity.d.ts +13 -0
- package/dist/database/entities/social-account.entity.js +54 -0
- package/dist/database/entities/social-account.entity.js.map +1 -0
- package/dist/database/entities/two-factor-secret.entity.d.ts +10 -0
- package/dist/database/entities/two-factor-secret.entity.js +48 -0
- package/dist/database/entities/two-factor-secret.entity.js.map +1 -0
- package/dist/database/entities/user.entity.d.ts +34 -0
- package/dist/database/entities/user.entity.js +129 -0
- package/dist/database/entities/user.entity.js.map +1 -0
- package/dist/database/entities/webhook-delivery.entity.d.ts +17 -0
- package/dist/database/entities/webhook-delivery.entity.js +79 -0
- package/dist/database/entities/webhook-delivery.entity.js.map +1 -0
- package/dist/database/entities/webhook.entity.d.ts +12 -0
- package/dist/database/entities/webhook.entity.js +58 -0
- package/dist/database/entities/webhook.entity.js.map +1 -0
- package/dist/database/typeorm-row.d.ts +21 -0
- package/dist/database/typeorm-row.js +29 -0
- package/dist/database/typeorm-row.js.map +1 -0
- package/dist/features/account/account.controller.d.ts +259 -0
- package/dist/features/account/account.controller.js +372 -0
- package/dist/features/account/account.controller.js.map +1 -0
- package/dist/features/account/account.dto.d.ts +16 -0
- package/dist/features/account/account.dto.js +61 -0
- package/dist/features/account/account.dto.js.map +1 -0
- package/dist/features/account/data-export.service.d.ts +85 -0
- package/dist/features/account/data-export.service.js +125 -0
- package/dist/features/account/data-export.service.js.map +1 -0
- package/dist/features/account/health.service.d.ts +30 -0
- package/dist/features/account/health.service.js +59 -0
- package/dist/features/account/health.service.js.map +1 -0
- package/dist/features/admin/admin.controller.d.ts +382 -0
- package/dist/features/admin/admin.controller.js +623 -0
- package/dist/features/admin/admin.controller.js.map +1 -0
- package/dist/features/admin/admin.dto.d.ts +61 -0
- package/dist/features/admin/admin.dto.js +253 -0
- package/dist/features/admin/admin.dto.js.map +1 -0
- package/dist/features/admin/admin.service.d.ts +142 -0
- package/dist/features/admin/admin.service.js +227 -0
- package/dist/features/admin/admin.service.js.map +1 -0
- package/dist/features/api-keys/api-keys.service.d.ts +67 -0
- package/dist/features/api-keys/api-keys.service.js +144 -0
- package/dist/features/api-keys/api-keys.service.js.map +1 -0
- package/dist/features/email-change/email-change.controller.d.ts +38 -0
- package/dist/features/email-change/email-change.controller.js +85 -0
- package/dist/features/email-change/email-change.controller.js.map +1 -0
- package/dist/features/email-change/email-change.dto.d.ts +10 -0
- package/dist/features/email-change/email-change.dto.js +43 -0
- package/dist/features/email-change/email-change.dto.js.map +1 -0
- package/dist/features/email-change/email-change.service.d.ts +74 -0
- package/dist/features/email-change/email-change.service.js +179 -0
- package/dist/features/email-change/email-change.service.js.map +1 -0
- package/dist/features/email-links/email-verification.service.d.ts +46 -0
- package/dist/features/email-links/email-verification.service.js +94 -0
- package/dist/features/email-links/email-verification.service.js.map +1 -0
- package/dist/features/email-links/link-tokens.service.d.ts +44 -0
- package/dist/features/email-links/link-tokens.service.js +122 -0
- package/dist/features/email-links/link-tokens.service.js.map +1 -0
- package/dist/features/events/auth-events.service.d.ts +52 -0
- package/dist/features/events/auth-events.service.js +117 -0
- package/dist/features/events/auth-events.service.js.map +1 -0
- package/dist/features/maintenance/data-retention.service.d.ts +60 -0
- package/dist/features/maintenance/data-retention.service.js +182 -0
- package/dist/features/maintenance/data-retention.service.js.map +1 -0
- package/dist/features/notifications/notifications.service.d.ts +40 -0
- package/dist/features/notifications/notifications.service.js +94 -0
- package/dist/features/notifications/notifications.service.js.map +1 -0
- package/dist/features/one-time-codes/one-time-codes.service.d.ts +40 -0
- package/dist/features/one-time-codes/one-time-codes.service.js +102 -0
- package/dist/features/one-time-codes/one-time-codes.service.js.map +1 -0
- package/dist/features/organizations/organizations.controller.d.ts +220 -0
- package/dist/features/organizations/organizations.controller.js +337 -0
- package/dist/features/organizations/organizations.controller.js.map +1 -0
- package/dist/features/organizations/organizations.dto.d.ts +31 -0
- package/dist/features/organizations/organizations.dto.js +100 -0
- package/dist/features/organizations/organizations.dto.js.map +1 -0
- package/dist/features/organizations/organizations.service.d.ts +274 -0
- package/dist/features/organizations/organizations.service.js +481 -0
- package/dist/features/organizations/organizations.service.js.map +1 -0
- package/dist/features/passkeys/passkeys.controller.d.ts +92 -0
- package/dist/features/passkeys/passkeys.controller.js +183 -0
- package/dist/features/passkeys/passkeys.controller.js.map +1 -0
- package/dist/features/passkeys/passkeys.dto.d.ts +13 -0
- package/dist/features/passkeys/passkeys.dto.js +50 -0
- package/dist/features/passkeys/passkeys.dto.js.map +1 -0
- package/dist/features/passkeys/passkeys.service.d.ts +159 -0
- package/dist/features/passkeys/passkeys.service.js +350 -0
- package/dist/features/passkeys/passkeys.service.js.map +1 -0
- package/dist/features/passwordless/passwordless-auth.service.d.ts +152 -0
- package/dist/features/passwordless/passwordless-auth.service.js +304 -0
- package/dist/features/passwordless/passwordless-auth.service.js.map +1 -0
- package/dist/features/passwordless/passwordless.controller.d.ts +75 -0
- package/dist/features/passwordless/passwordless.controller.js +163 -0
- package/dist/features/passwordless/passwordless.controller.js.map +1 -0
- package/dist/features/passwordless/passwordless.dto.d.ts +29 -0
- package/dist/features/passwordless/passwordless.dto.js +101 -0
- package/dist/features/passwordless/passwordless.dto.js.map +1 -0
- package/dist/features/passwords/password-auth.service.d.ts +154 -0
- package/dist/features/passwords/password-auth.service.js +317 -0
- package/dist/features/passwords/password-auth.service.js.map +1 -0
- package/dist/features/passwords/password-policy.service.d.ts +63 -0
- package/dist/features/passwords/password-policy.service.js +175 -0
- package/dist/features/passwords/password-policy.service.js.map +1 -0
- package/dist/features/passwords/password.controller.d.ts +95 -0
- package/dist/features/passwords/password.controller.js +193 -0
- package/dist/features/passwords/password.controller.js.map +1 -0
- package/dist/features/passwords/password.dto.d.ts +36 -0
- package/dist/features/passwords/password.dto.js +140 -0
- package/dist/features/passwords/password.dto.js.map +1 -0
- package/dist/features/sessions/session.service.d.ts +132 -0
- package/dist/features/sessions/session.service.js +308 -0
- package/dist/features/sessions/session.service.js.map +1 -0
- package/dist/features/sessions/session.types.d.ts +34 -0
- package/dist/features/sessions/session.types.js +3 -0
- package/dist/features/sessions/session.types.js.map +1 -0
- package/dist/features/settings/settings.defaults.d.ts +2 -0
- package/dist/features/settings/settings.defaults.js +7 -0
- package/dist/features/settings/settings.defaults.js.map +1 -0
- package/dist/features/settings/settings.service.d.ts +44 -0
- package/dist/features/settings/settings.service.js +112 -0
- package/dist/features/settings/settings.service.js.map +1 -0
- package/dist/features/settings/settings.types.d.ts +133 -0
- package/dist/features/settings/settings.types.js +3 -0
- package/dist/features/settings/settings.types.js.map +1 -0
- package/dist/features/settings/settings.validation.d.ts +33 -0
- package/dist/features/settings/settings.validation.js +614 -0
- package/dist/features/settings/settings.validation.js.map +1 -0
- package/dist/features/sign-in/sign-in-challenges.service.d.ts +54 -0
- package/dist/features/sign-in/sign-in-challenges.service.js +104 -0
- package/dist/features/sign-in/sign-in-challenges.service.js.map +1 -0
- package/dist/features/sign-in/sign-in.service.d.ts +127 -0
- package/dist/features/sign-in/sign-in.service.js +248 -0
- package/dist/features/sign-in/sign-in.service.js.map +1 -0
- package/dist/features/sign-in/sign-in.types.d.ts +22 -0
- package/dist/features/sign-in/sign-in.types.js +3 -0
- package/dist/features/sign-in/sign-in.types.js.map +1 -0
- package/dist/features/signing-keys/jwks.controller.d.ts +17 -0
- package/dist/features/signing-keys/jwks.controller.js +51 -0
- package/dist/features/signing-keys/jwks.controller.js.map +1 -0
- package/dist/features/signing-keys/signing-keys.service.d.ts +67 -0
- package/dist/features/signing-keys/signing-keys.service.js +142 -0
- package/dist/features/signing-keys/signing-keys.service.js.map +1 -0
- package/dist/features/social/social-auth.service.d.ts +100 -0
- package/dist/features/social/social-auth.service.js +232 -0
- package/dist/features/social/social-auth.service.js.map +1 -0
- package/dist/features/social/social-providers.service.d.ts +97 -0
- package/dist/features/social/social-providers.service.js +214 -0
- package/dist/features/social/social-providers.service.js.map +1 -0
- package/dist/features/social/social.controller.d.ts +54 -0
- package/dist/features/social/social.controller.js +124 -0
- package/dist/features/social/social.controller.js.map +1 -0
- package/dist/features/social/social.dto.d.ts +11 -0
- package/dist/features/social/social.dto.js +38 -0
- package/dist/features/social/social.dto.js.map +1 -0
- package/dist/features/two-factor/two-factor.controller.d.ts +84 -0
- package/dist/features/two-factor/two-factor.controller.js +174 -0
- package/dist/features/two-factor/two-factor.controller.js.map +1 -0
- package/dist/features/two-factor/two-factor.dto.d.ts +10 -0
- package/dist/features/two-factor/two-factor.dto.js +37 -0
- package/dist/features/two-factor/two-factor.dto.js.map +1 -0
- package/dist/features/two-factor/two-factor.service.d.ts +171 -0
- package/dist/features/two-factor/two-factor.service.js +348 -0
- package/dist/features/two-factor/two-factor.service.js.map +1 -0
- package/dist/features/users/sign-in-methods.service.d.ts +40 -0
- package/dist/features/users/sign-in-methods.service.js +81 -0
- package/dist/features/users/sign-in-methods.service.js.map +1 -0
- package/dist/features/users/sign-up-rules.d.ts +12 -0
- package/dist/features/users/sign-up-rules.js +29 -0
- package/dist/features/users/sign-up-rules.js.map +1 -0
- package/dist/features/users/users.service.d.ts +246 -0
- package/dist/features/users/users.service.js +509 -0
- package/dist/features/users/users.service.js.map +1 -0
- package/dist/features/users/users.types.d.ts +24 -0
- package/dist/features/users/users.types.js +3 -0
- package/dist/features/users/users.types.js.map +1 -0
- package/dist/features/webhooks/webhook-delivery.worker.d.ts +55 -0
- package/dist/features/webhooks/webhook-delivery.worker.js +175 -0
- package/dist/features/webhooks/webhook-delivery.worker.js.map +1 -0
- package/dist/features/webhooks/webhook-signature.d.ts +28 -0
- package/dist/features/webhooks/webhook-signature.js +85 -0
- package/dist/features/webhooks/webhook-signature.js.map +1 -0
- package/dist/features/webhooks/webhooks.service.d.ts +144 -0
- package/dist/features/webhooks/webhooks.service.js +238 -0
- package/dist/features/webhooks/webhooks.service.js.map +1 -0
- package/dist/guards/auth.decorators.d.ts +65 -0
- package/dist/guards/auth.decorators.js +68 -0
- package/dist/guards/auth.decorators.js.map +1 -0
- package/dist/guards/auth.guard.d.ts +47 -0
- package/dist/guards/auth.guard.js +132 -0
- package/dist/guards/auth.guard.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/console-email-sender.d.ts +15 -0
- package/dist/integrations/console-email-sender.js +26 -0
- package/dist/integrations/console-email-sender.js.map +1 -0
- package/dist/integrations/msg91-sms-sender.d.ts +28 -0
- package/dist/integrations/msg91-sms-sender.js +46 -0
- package/dist/integrations/msg91-sms-sender.js.map +1 -0
- package/docs/README.md +39 -0
- package/docs/admin-api-keys-webhooks.md +147 -0
- package/docs/api-reference.md +175 -0
- package/docs/architecture.md +119 -0
- package/docs/client-integration.md +192 -0
- package/docs/code-walkthrough.md +273 -0
- package/docs/configuration.md +229 -0
- package/docs/endpoints/README.md +151 -0
- package/docs/endpoints/account.md +363 -0
- package/docs/endpoints/admin.md +422 -0
- package/docs/endpoints/email-password.md +261 -0
- package/docs/endpoints/organizations.md +192 -0
- package/docs/endpoints/passkeys.md +195 -0
- package/docs/endpoints/passwordless.md +214 -0
- package/docs/endpoints/social.md +116 -0
- package/docs/endpoints/two-factor.md +146 -0
- package/docs/error-codes.md +100 -0
- package/docs/events.md +83 -0
- package/docs/getting-started.md +139 -0
- package/docs/manual-testing.md +215 -0
- package/docs/organizations.md +86 -0
- package/docs/production-checklist.md +60 -0
- package/docs/protecting-routes.md +147 -0
- package/docs/security.md +61 -0
- package/docs/sign-in-flows.md +294 -0
- package/package.json +92 -4
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { AuthOptions } from '../../auth-options.js';
|
|
2
|
+
import { CHALLENGE_PURPOSE } from '../../constants.js';
|
|
3
|
+
import { ChallengePurpose } from '../../database/entities/index.js';
|
|
4
|
+
import { SignInMethod } from '../sessions/session.types.js';
|
|
5
|
+
/** A challenge that has been checked and is ready to finish. */
|
|
6
|
+
export interface OpenChallenge {
|
|
7
|
+
challengeId: string;
|
|
8
|
+
userId: string;
|
|
9
|
+
signInMethod: SignInMethod;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Short-lived "half done" tokens (prefix "ch_"):
|
|
13
|
+
* - two_factor: the first step passed, the 2FA code is next.
|
|
14
|
+
* - reactivate_account: a DEACTIVATED user proved who they are and may now reactivate.
|
|
15
|
+
*
|
|
16
|
+
* Only the token's hash is stored. A token can be used once, and a few wrong 2FA codes burn it.
|
|
17
|
+
*/
|
|
18
|
+
export declare class SignInChallengesService {
|
|
19
|
+
private readonly database;
|
|
20
|
+
constructor(options: AuthOptions);
|
|
21
|
+
/**
|
|
22
|
+
* Creates a challenge and returns the plain token to give the client. Only its hash is stored.
|
|
23
|
+
*
|
|
24
|
+
* @param purpose - "two_factor" or "reactivate_account".
|
|
25
|
+
* @param userId - The user who passed the first step.
|
|
26
|
+
* @param signInMethod - How they passed it (kept for the final sign-in).
|
|
27
|
+
* @param lifetimeSeconds - How long the token works.
|
|
28
|
+
* @returns The plain "ch_..." token.
|
|
29
|
+
*/
|
|
30
|
+
createChallenge(purpose: typeof CHALLENGE_PURPOSE.twoFactor | typeof CHALLENGE_PURPOSE.reactivateAccount, userId: string, signInMethod: SignInMethod, lifetimeSeconds: number): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Looks up a challenge token without using it up.
|
|
33
|
+
*
|
|
34
|
+
* @param challengeToken - The token the client sent.
|
|
35
|
+
* @param purpose - What the token must be for.
|
|
36
|
+
* @returns The challenge's id, user and sign-in method.
|
|
37
|
+
* @throws AuthError INVALID_TOKEN if the token is unknown, already used, expired,
|
|
38
|
+
* or has had too many wrong codes.
|
|
39
|
+
*/
|
|
40
|
+
findOpenChallenge(challengeToken: unknown, purpose: ChallengePurpose): Promise<OpenChallenge>;
|
|
41
|
+
/**
|
|
42
|
+
* Marks the challenge used, so the same step can never complete twice.
|
|
43
|
+
*
|
|
44
|
+
* @param challengeId - From `findOpenChallenge`.
|
|
45
|
+
* @returns False if another request used it first.
|
|
46
|
+
*/
|
|
47
|
+
markChallengeUsed(challengeId: string): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* Counts a wrong code. After TWO_FACTOR_MAX_ATTEMPTS the challenge stops working.
|
|
50
|
+
*
|
|
51
|
+
* @param challengeId - From `findOpenChallenge`.
|
|
52
|
+
*/
|
|
53
|
+
recordFailedAttempt(challengeId: string): Promise<void>;
|
|
54
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.SignInChallengesService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const typeorm_1 = require("typeorm");
|
|
18
|
+
const auth_error_1 = require("../../common/auth-error.js");
|
|
19
|
+
const random_secrets_1 = require("../../common/crypto/random-secrets.js");
|
|
20
|
+
const constants_1 = require("../../constants.js");
|
|
21
|
+
const entities_1 = require("../../database/entities/index.js");
|
|
22
|
+
/**
|
|
23
|
+
* Short-lived "half done" tokens (prefix "ch_"):
|
|
24
|
+
* - two_factor: the first step passed, the 2FA code is next.
|
|
25
|
+
* - reactivate_account: a DEACTIVATED user proved who they are and may now reactivate.
|
|
26
|
+
*
|
|
27
|
+
* Only the token's hash is stored. A token can be used once, and a few wrong 2FA codes burn it.
|
|
28
|
+
*/
|
|
29
|
+
let SignInChallengesService = class SignInChallengesService {
|
|
30
|
+
database;
|
|
31
|
+
constructor(options) {
|
|
32
|
+
this.database = options.dataSource;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Creates a challenge and returns the plain token to give the client. Only its hash is stored.
|
|
36
|
+
*
|
|
37
|
+
* @param purpose - "two_factor" or "reactivate_account".
|
|
38
|
+
* @param userId - The user who passed the first step.
|
|
39
|
+
* @param signInMethod - How they passed it (kept for the final sign-in).
|
|
40
|
+
* @param lifetimeSeconds - How long the token works.
|
|
41
|
+
* @returns The plain "ch_..." token.
|
|
42
|
+
*/
|
|
43
|
+
async createChallenge(purpose, userId, signInMethod, lifetimeSeconds) {
|
|
44
|
+
const { plainToken, tokenHash } = (0, random_secrets_1.createRandomToken)(constants_1.TOKEN_PREFIX.signInChallenge);
|
|
45
|
+
await this.database.getRepository(entities_1.SignInChallengeEntity).insert({
|
|
46
|
+
purpose,
|
|
47
|
+
userId,
|
|
48
|
+
signInMethod,
|
|
49
|
+
challengeTokenHash: tokenHash,
|
|
50
|
+
expiresAt: new Date(Date.now() + lifetimeSeconds * constants_1.SECOND_IN_MILLISECONDS),
|
|
51
|
+
});
|
|
52
|
+
return plainToken;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Looks up a challenge token without using it up.
|
|
56
|
+
*
|
|
57
|
+
* @param challengeToken - The token the client sent.
|
|
58
|
+
* @param purpose - What the token must be for.
|
|
59
|
+
* @returns The challenge's id, user and sign-in method.
|
|
60
|
+
* @throws AuthError INVALID_TOKEN if the token is unknown, already used, expired,
|
|
61
|
+
* or has had too many wrong codes.
|
|
62
|
+
*/
|
|
63
|
+
async findOpenChallenge(challengeToken, purpose) {
|
|
64
|
+
if (!(0, random_secrets_1.hasRandomTokenFormat)(challengeToken, constants_1.TOKEN_PREFIX.signInChallenge))
|
|
65
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidOrExpiredLink);
|
|
66
|
+
const challenge = await this.database.getRepository(entities_1.SignInChallengeEntity).findOne({
|
|
67
|
+
where: { challengeTokenHash: (0, random_secrets_1.sha256Hex)(challengeToken), purpose, usedAt: (0, typeorm_1.IsNull)() },
|
|
68
|
+
});
|
|
69
|
+
const isUsable = challenge &&
|
|
70
|
+
challenge.userId &&
|
|
71
|
+
challenge.expiresAt.getTime() > Date.now() &&
|
|
72
|
+
challenge.failedAttempts < constants_1.TWO_FACTOR_MAX_ATTEMPTS;
|
|
73
|
+
if (!isUsable)
|
|
74
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidOrExpiredLink);
|
|
75
|
+
return { challengeId: challenge.id, userId: challenge.userId, signInMethod: challenge.signInMethod };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Marks the challenge used, so the same step can never complete twice.
|
|
79
|
+
*
|
|
80
|
+
* @param challengeId - From `findOpenChallenge`.
|
|
81
|
+
* @returns False if another request used it first.
|
|
82
|
+
*/
|
|
83
|
+
async markChallengeUsed(challengeId) {
|
|
84
|
+
const result = await this.database
|
|
85
|
+
.getRepository(entities_1.SignInChallengeEntity)
|
|
86
|
+
.update({ id: challengeId, usedAt: (0, typeorm_1.IsNull)() }, { usedAt: new Date() });
|
|
87
|
+
return (result.affected ?? 0) > 0;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Counts a wrong code. After TWO_FACTOR_MAX_ATTEMPTS the challenge stops working.
|
|
91
|
+
*
|
|
92
|
+
* @param challengeId - From `findOpenChallenge`.
|
|
93
|
+
*/
|
|
94
|
+
async recordFailedAttempt(challengeId) {
|
|
95
|
+
await this.database.getRepository(entities_1.SignInChallengeEntity).increment({ id: challengeId }, 'failedAttempts', 1);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
exports.SignInChallengesService = SignInChallengesService;
|
|
99
|
+
exports.SignInChallengesService = SignInChallengesService = __decorate([
|
|
100
|
+
(0, common_1.Injectable)(),
|
|
101
|
+
__param(0, (0, common_1.Inject)(constants_1.AUTH_OPTIONS_TOKEN)),
|
|
102
|
+
__metadata("design:paramtypes", [Object])
|
|
103
|
+
], SignInChallengesService);
|
|
104
|
+
//# sourceMappingURL=sign-in-challenges.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign-in-challenges.service.js","sourceRoot":"","sources":["../../../src/features/sign-in/sign-in-challenges.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoD;AACpD,qCAA6C;AAE7C,oDAAgD;AAChD,mEAAoG;AACpG,2CAOqB;AACrB,kDAA8E;AAU9E;;;;;;GAMG;AAEI,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IACjB,QAAQ,CAAa;IAEtC,YAAwC,OAAoB;QAC1D,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;IACrC,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,eAAe,CACnB,OAAwF,EACxF,MAAc,EACd,YAA0B,EAC1B,eAAuB;QAEvB,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,IAAA,kCAAiB,EAAC,wBAAY,CAAC,eAAe,CAAC,CAAC;QAClF,MAAM,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,gCAAqB,CAAC,CAAC,MAAM,CAAC;YAC9D,OAAO;YACP,MAAM;YACN,YAAY;YACZ,kBAAkB,EAAE,SAAS;YAC7B,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,GAAG,kCAAsB,CAAC;SAC3E,CAAC,CAAC;QACH,OAAO,UAAU,CAAC;IACpB,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,iBAAiB,CAAC,cAAuB,EAAE,OAAyB;QACxE,IAAI,CAAC,IAAA,qCAAoB,EAAC,cAAc,EAAE,wBAAY,CAAC,eAAe,CAAC;YAAE,MAAM,IAAI,sBAAS,CAAC,uBAAW,CAAC,oBAAoB,CAAC,CAAC;QAC/H,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,gCAAqB,CAAC,CAAC,OAAO,CAAC;YACjF,KAAK,EAAE,EAAE,kBAAkB,EAAE,IAAA,0BAAS,EAAC,cAAc,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAA,gBAAM,GAAE,EAAE;SACpF,CAAC,CAAC;QACH,MAAM,QAAQ,GACZ,SAAS;YACT,SAAS,CAAC,MAAM;YAChB,SAAS,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE;YAC1C,SAAS,CAAC,cAAc,GAAG,mCAAuB,CAAC;QACrD,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,sBAAS,CAAC,uBAAW,CAAC,oBAAoB,CAAC,CAAC;QACrE,OAAO,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,MAAO,EAAE,YAAY,EAAE,SAAS,CAAC,YAA4B,EAAE,CAAC;IACxH,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,iBAAiB,CAAC,WAAmB;QACzC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ;aAC/B,aAAa,CAAC,gCAAqB,CAAC;aACpC,MAAM,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,IAAA,gBAAM,GAAE,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;QACzE,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,mBAAmB,CAAC,WAAmB;QAC3C,MAAM,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,gCAAqB,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC;IAC/G,CAAC;CACF,CAAA;AA7EY,0DAAuB;kCAAvB,uBAAuB;IADnC,IAAA,mBAAU,GAAE;IAIE,WAAA,IAAA,eAAM,EAAC,8BAAkB,CAAC,CAAA;;GAH5B,uBAAuB,CA6EnC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { AuthOptions } from '../../auth-options.js';
|
|
2
|
+
import { RequestContext } from '../../common/request-context.js';
|
|
3
|
+
import { type SignInFailureReason } from '../../constants.js';
|
|
4
|
+
import { UserEntity } from '../../database/entities/index.js';
|
|
5
|
+
import { AuthEventsService } from '../events/auth-events.service.js';
|
|
6
|
+
import { NotificationsService } from '../notifications/notifications.service.js';
|
|
7
|
+
import { SessionService } from '../sessions/session.service.js';
|
|
8
|
+
import { SignInMethod } from '../sessions/session.types.js';
|
|
9
|
+
import { AuthSettings } from '../settings/settings.types.js';
|
|
10
|
+
import { UsersService } from '../users/users.service.js';
|
|
11
|
+
import { SignInChallengesService } from './sign-in-challenges.service';
|
|
12
|
+
import { SignedInResult, SignInResult } from './sign-in.types';
|
|
13
|
+
/**
|
|
14
|
+
* The one path every sign-in method ends in (password, codes, magic link, Google, passkeys...).
|
|
15
|
+
* Each method only proves who the user is; this service then decides what happens:
|
|
16
|
+
*
|
|
17
|
+
* - Is the account allowed to sign in? Suspended, deactivated or unverified accounts stop here
|
|
18
|
+
* with a clear error code.
|
|
19
|
+
* - Does the user have 2FA turned on? Then a challenge token is returned instead of tokens.
|
|
20
|
+
* - Otherwise a session starts and tokens are returned (plus the "new sign-in" email and event).
|
|
21
|
+
*
|
|
22
|
+
* Keeping this in one place means a new sign-in method can never forget a security check.
|
|
23
|
+
*/
|
|
24
|
+
export declare class SignInService {
|
|
25
|
+
private readonly users;
|
|
26
|
+
private readonly sessions;
|
|
27
|
+
private readonly challenges;
|
|
28
|
+
private readonly notifications;
|
|
29
|
+
private readonly events;
|
|
30
|
+
private readonly database;
|
|
31
|
+
constructor(options: AuthOptions, users: UsersService, sessions: SessionService, challenges: SignInChallengesService, notifications: NotificationsService, events: AuthEventsService);
|
|
32
|
+
/**
|
|
33
|
+
* Called by a sign-in method once the user has proven who they are.
|
|
34
|
+
*
|
|
35
|
+
* @param user - The user who proved who they are.
|
|
36
|
+
* @param signInMethod - How they did it (password, passkey, google...).
|
|
37
|
+
* @param request - Who is asking (for the session, emails and audit log).
|
|
38
|
+
* @param settings - The current settings.
|
|
39
|
+
* @returns Either tokens (`status: "signed_in"`), or a challenge token (`status: "two_factor_required"`).
|
|
40
|
+
* @throws AuthError ACCOUNT_NOT_VERIFIED, ACCOUNT_DEACTIVATED or ACCOUNT_SUSPENDED (see `assertAccountCanSignIn`).
|
|
41
|
+
*/
|
|
42
|
+
completeSignIn(user: UserEntity, signInMethod: SignInMethod, request: RequestContext, settings: AuthSettings): Promise<SignInResult>;
|
|
43
|
+
/**
|
|
44
|
+
* Called by the 2FA service after a correct authenticator or backup code. Checks the account
|
|
45
|
+
* again, because it may have been suspended in the minutes since the first step.
|
|
46
|
+
*
|
|
47
|
+
* @param user - The user who passed both steps.
|
|
48
|
+
* @param signInMethod - The method used for the first step.
|
|
49
|
+
* @param request - Who is asking.
|
|
50
|
+
* @param settings - The current settings.
|
|
51
|
+
* @returns Access and refresh tokens.
|
|
52
|
+
*/
|
|
53
|
+
finishTwoFactorSignIn(user: UserEntity, signInMethod: SignInMethod, request: RequestContext, settings: AuthSettings): Promise<SignedInResult>;
|
|
54
|
+
/**
|
|
55
|
+
* A deactivated user proved who they are (the sign-in returned ACCOUNT_DEACTIVATED with a
|
|
56
|
+
* reactivationToken). This switches the account back on and signs them in.
|
|
57
|
+
*
|
|
58
|
+
* @param reactivationToken - The token from the ACCOUNT_DEACTIVATED error.
|
|
59
|
+
* @param request - Who is asking.
|
|
60
|
+
* @param settings - The current settings.
|
|
61
|
+
* @returns The same result as `completeSignIn` (tokens, or a 2FA challenge).
|
|
62
|
+
* @throws AuthError INVALID_TOKEN when the token is unknown, used or expired.
|
|
63
|
+
*/
|
|
64
|
+
reactivateAndSignIn(reactivationToken: unknown, request: RequestContext, settings: AuthSettings): Promise<SignInResult>;
|
|
65
|
+
/**
|
|
66
|
+
* Signs out the device that made the request.
|
|
67
|
+
*
|
|
68
|
+
* @param userId - The signed-in user.
|
|
69
|
+
* @param sessionId - The session to end (from the access token).
|
|
70
|
+
* @param request - Who is asking (for the audit log).
|
|
71
|
+
*/
|
|
72
|
+
signOut(userId: string, sessionId: string, request: RequestContext): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Signs out every device of the user.
|
|
75
|
+
*
|
|
76
|
+
* @param userId - The signed-in user.
|
|
77
|
+
* @param request - Who is asking (for the audit log).
|
|
78
|
+
* @param keepSessionId - When given, this device stays signed in.
|
|
79
|
+
* @returns How many sessions were ended.
|
|
80
|
+
*/
|
|
81
|
+
signOutEverywhere(userId: string, request: RequestContext, keepSessionId?: string): Promise<number>;
|
|
82
|
+
/**
|
|
83
|
+
* Records a failed attempt in the audit log (wrong password, wrong code...).
|
|
84
|
+
*
|
|
85
|
+
* @param userId - The user it was for, or null when the account is unknown.
|
|
86
|
+
* @param signInMethod - The method that was tried.
|
|
87
|
+
* @param reason - Why it failed.
|
|
88
|
+
* @param request - Who tried.
|
|
89
|
+
*/
|
|
90
|
+
recordFailedSignIn(userId: string | null, signInMethod: SignInMethod, reason: SignInFailureReason, request: RequestContext): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Stops sign-in for accounts that may not sign in, with a code the app can act on.
|
|
93
|
+
* These run only AFTER the user proved who they are, so they reveal nothing to a stranger.
|
|
94
|
+
*
|
|
95
|
+
* A pending account whose verifications are all done is activated here.
|
|
96
|
+
*
|
|
97
|
+
* @param user - The user signing in.
|
|
98
|
+
* @param signInMethod - How they proved who they are.
|
|
99
|
+
* @param request - Who is asking.
|
|
100
|
+
* @param settings - The current settings.
|
|
101
|
+
* @throws AuthError ACCOUNT_NOT_VERIFIED with `pendingVerifications`, so the app can show the right screen.
|
|
102
|
+
* @throws AuthError ACCOUNT_DEACTIVATED with a `reactivationToken` for `reactivateAndSignIn`.
|
|
103
|
+
* @throws AuthError ACCOUNT_SUSPENDED when an admin suspended the account.
|
|
104
|
+
* @throws AuthError INVALID_CREDENTIALS for deleted accounts (so they look like unknown ones).
|
|
105
|
+
*/
|
|
106
|
+
private assertAccountCanSignIn;
|
|
107
|
+
/**
|
|
108
|
+
* Starts the session, records the sign-in, and sends the "new sign-in" email for a new device.
|
|
109
|
+
*
|
|
110
|
+
* @param user - The user signing in.
|
|
111
|
+
* @param signInMethod - How they signed in.
|
|
112
|
+
* @param usedTwoFactor - Whether a second factor was used (recorded in the session and tokens).
|
|
113
|
+
* @param request - Who is asking.
|
|
114
|
+
* @param settings - The current settings.
|
|
115
|
+
* @returns Tokens plus the user's public profile.
|
|
116
|
+
*/
|
|
117
|
+
private startSignedInSession;
|
|
118
|
+
/**
|
|
119
|
+
* "New device" = the user has signed in before, but never with this browser/app (user agent).
|
|
120
|
+
* The very first sign-in is not reported, since the user just created the account.
|
|
121
|
+
*
|
|
122
|
+
* @param userId - The user signing in.
|
|
123
|
+
* @param request - Holds the user agent.
|
|
124
|
+
* @returns True when the "new sign-in" email should be sent.
|
|
125
|
+
*/
|
|
126
|
+
private isNewDeviceForUser;
|
|
127
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.SignInService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const auth_error_1 = require("../../common/auth-error.js");
|
|
18
|
+
const constants_1 = require("../../constants.js");
|
|
19
|
+
const entities_1 = require("../../database/entities/index.js");
|
|
20
|
+
const auth_events_service_1 = require("../events/auth-events.service.js");
|
|
21
|
+
const notifications_service_1 = require("../notifications/notifications.service.js");
|
|
22
|
+
const session_service_1 = require("../sessions/session.service.js");
|
|
23
|
+
const users_service_1 = require("../users/users.service.js");
|
|
24
|
+
const sign_in_challenges_service_1 = require("./sign-in-challenges.service");
|
|
25
|
+
/**
|
|
26
|
+
* The one path every sign-in method ends in (password, codes, magic link, Google, passkeys...).
|
|
27
|
+
* Each method only proves who the user is; this service then decides what happens:
|
|
28
|
+
*
|
|
29
|
+
* - Is the account allowed to sign in? Suspended, deactivated or unverified accounts stop here
|
|
30
|
+
* with a clear error code.
|
|
31
|
+
* - Does the user have 2FA turned on? Then a challenge token is returned instead of tokens.
|
|
32
|
+
* - Otherwise a session starts and tokens are returned (plus the "new sign-in" email and event).
|
|
33
|
+
*
|
|
34
|
+
* Keeping this in one place means a new sign-in method can never forget a security check.
|
|
35
|
+
*/
|
|
36
|
+
let SignInService = class SignInService {
|
|
37
|
+
users;
|
|
38
|
+
sessions;
|
|
39
|
+
challenges;
|
|
40
|
+
notifications;
|
|
41
|
+
events;
|
|
42
|
+
database;
|
|
43
|
+
constructor(options, users, sessions, challenges, notifications, events) {
|
|
44
|
+
this.users = users;
|
|
45
|
+
this.sessions = sessions;
|
|
46
|
+
this.challenges = challenges;
|
|
47
|
+
this.notifications = notifications;
|
|
48
|
+
this.events = events;
|
|
49
|
+
this.database = options.dataSource;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Called by a sign-in method once the user has proven who they are.
|
|
53
|
+
*
|
|
54
|
+
* @param user - The user who proved who they are.
|
|
55
|
+
* @param signInMethod - How they did it (password, passkey, google...).
|
|
56
|
+
* @param request - Who is asking (for the session, emails and audit log).
|
|
57
|
+
* @param settings - The current settings.
|
|
58
|
+
* @returns Either tokens (`status: "signed_in"`), or a challenge token (`status: "two_factor_required"`).
|
|
59
|
+
* @throws AuthError ACCOUNT_NOT_VERIFIED, ACCOUNT_DEACTIVATED or ACCOUNT_SUSPENDED (see `assertAccountCanSignIn`).
|
|
60
|
+
*/
|
|
61
|
+
async completeSignIn(user, signInMethod, request, settings) {
|
|
62
|
+
await this.assertAccountCanSignIn(user, signInMethod, request, settings);
|
|
63
|
+
// Passkeys already combine "something you have" with a fingerprint/PIN, so they count as two factors.
|
|
64
|
+
const needsTwoFactor = settings.twoFactor.enabled && user.isTwoFactorEnabled && signInMethod !== constants_1.SIGN_IN_METHOD.passkey;
|
|
65
|
+
if (needsTwoFactor) {
|
|
66
|
+
const challengeSeconds = settings.twoFactor.challengeExpiresInMinutes * constants_1.MINUTE_IN_SECONDS;
|
|
67
|
+
const challengeToken = await this.challenges.createChallenge(constants_1.CHALLENGE_PURPOSE.twoFactor, user.id, signInMethod, challengeSeconds);
|
|
68
|
+
return { success: true, status: constants_1.SIGN_IN_STATUS.twoFactorRequired, challengeToken, expiresIn: challengeSeconds };
|
|
69
|
+
}
|
|
70
|
+
return this.startSignedInSession(user, signInMethod, signInMethod === constants_1.SIGN_IN_METHOD.passkey, request, settings);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Called by the 2FA service after a correct authenticator or backup code. Checks the account
|
|
74
|
+
* again, because it may have been suspended in the minutes since the first step.
|
|
75
|
+
*
|
|
76
|
+
* @param user - The user who passed both steps.
|
|
77
|
+
* @param signInMethod - The method used for the first step.
|
|
78
|
+
* @param request - Who is asking.
|
|
79
|
+
* @param settings - The current settings.
|
|
80
|
+
* @returns Access and refresh tokens.
|
|
81
|
+
*/
|
|
82
|
+
async finishTwoFactorSignIn(user, signInMethod, request, settings) {
|
|
83
|
+
await this.assertAccountCanSignIn(user, signInMethod, request, settings);
|
|
84
|
+
return this.startSignedInSession(user, signInMethod, true, request, settings);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A deactivated user proved who they are (the sign-in returned ACCOUNT_DEACTIVATED with a
|
|
88
|
+
* reactivationToken). This switches the account back on and signs them in.
|
|
89
|
+
*
|
|
90
|
+
* @param reactivationToken - The token from the ACCOUNT_DEACTIVATED error.
|
|
91
|
+
* @param request - Who is asking.
|
|
92
|
+
* @param settings - The current settings.
|
|
93
|
+
* @returns The same result as `completeSignIn` (tokens, or a 2FA challenge).
|
|
94
|
+
* @throws AuthError INVALID_TOKEN when the token is unknown, used or expired.
|
|
95
|
+
*/
|
|
96
|
+
async reactivateAndSignIn(reactivationToken, request, settings) {
|
|
97
|
+
const challenge = await this.challenges.findOpenChallenge(reactivationToken, constants_1.CHALLENGE_PURPOSE.reactivateAccount);
|
|
98
|
+
if (!(await this.challenges.markChallengeUsed(challenge.challengeId)))
|
|
99
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidOrExpiredLink);
|
|
100
|
+
const reactivatedUser = await this.users.reactivateAccount(challenge.userId, settings, request);
|
|
101
|
+
return this.completeSignIn(reactivatedUser, challenge.signInMethod, request, settings);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Signs out the device that made the request.
|
|
105
|
+
*
|
|
106
|
+
* @param userId - The signed-in user.
|
|
107
|
+
* @param sessionId - The session to end (from the access token).
|
|
108
|
+
* @param request - Who is asking (for the audit log).
|
|
109
|
+
*/
|
|
110
|
+
async signOut(userId, sessionId, request) {
|
|
111
|
+
await this.sessions.revokeSession(sessionId, constants_1.SESSION_END_REASON.signedOut, userId);
|
|
112
|
+
await this.events.record({ name: constants_1.AUTH_EVENTS.userSignedOut, userId, request, details: { sessionId } });
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Signs out every device of the user.
|
|
116
|
+
*
|
|
117
|
+
* @param userId - The signed-in user.
|
|
118
|
+
* @param request - Who is asking (for the audit log).
|
|
119
|
+
* @param keepSessionId - When given, this device stays signed in.
|
|
120
|
+
* @returns How many sessions were ended.
|
|
121
|
+
*/
|
|
122
|
+
async signOutEverywhere(userId, request, keepSessionId) {
|
|
123
|
+
const endedSessions = await this.sessions.revokeAllSessions(userId, constants_1.SESSION_END_REASON.signedOutEverywhere, keepSessionId);
|
|
124
|
+
await this.events.record({
|
|
125
|
+
name: constants_1.AUTH_EVENTS.userSignedOut,
|
|
126
|
+
userId,
|
|
127
|
+
request,
|
|
128
|
+
details: { everywhere: true, endedSessions },
|
|
129
|
+
});
|
|
130
|
+
return endedSessions;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Records a failed attempt in the audit log (wrong password, wrong code...).
|
|
134
|
+
*
|
|
135
|
+
* @param userId - The user it was for, or null when the account is unknown.
|
|
136
|
+
* @param signInMethod - The method that was tried.
|
|
137
|
+
* @param reason - Why it failed.
|
|
138
|
+
* @param request - Who tried.
|
|
139
|
+
*/
|
|
140
|
+
async recordFailedSignIn(userId, signInMethod, reason, request) {
|
|
141
|
+
await this.events.record({ name: constants_1.AUTH_EVENTS.userSignInFailed, userId, request, details: { signInMethod, reason } });
|
|
142
|
+
}
|
|
143
|
+
// -------------------------------------------------------------------------------------------
|
|
144
|
+
/**
|
|
145
|
+
* Stops sign-in for accounts that may not sign in, with a code the app can act on.
|
|
146
|
+
* These run only AFTER the user proved who they are, so they reveal nothing to a stranger.
|
|
147
|
+
*
|
|
148
|
+
* A pending account whose verifications are all done is activated here.
|
|
149
|
+
*
|
|
150
|
+
* @param user - The user signing in.
|
|
151
|
+
* @param signInMethod - How they proved who they are.
|
|
152
|
+
* @param request - Who is asking.
|
|
153
|
+
* @param settings - The current settings.
|
|
154
|
+
* @throws AuthError ACCOUNT_NOT_VERIFIED with `pendingVerifications`, so the app can show the right screen.
|
|
155
|
+
* @throws AuthError ACCOUNT_DEACTIVATED with a `reactivationToken` for `reactivateAndSignIn`.
|
|
156
|
+
* @throws AuthError ACCOUNT_SUSPENDED when an admin suspended the account.
|
|
157
|
+
* @throws AuthError INVALID_CREDENTIALS for deleted accounts (so they look like unknown ones).
|
|
158
|
+
*/
|
|
159
|
+
async assertAccountCanSignIn(user, signInMethod, request, settings) {
|
|
160
|
+
switch (user.status) {
|
|
161
|
+
case constants_1.ACCOUNT_STATUS.active:
|
|
162
|
+
return;
|
|
163
|
+
case constants_1.ACCOUNT_STATUS.pendingVerification: {
|
|
164
|
+
// The app sends the user to the right verification screen using `pendingVerifications`.
|
|
165
|
+
const pendingVerifications = this.users.getPendingVerifications(user, settings);
|
|
166
|
+
if (pendingVerifications.length === 0) {
|
|
167
|
+
await this.users.activateIfReady(user, settings, request);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
await this.recordFailedSignIn(user.id, signInMethod, constants_1.SIGN_IN_FAILURE_REASON.accountNotVerified, request);
|
|
171
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.accountNotVerified, {
|
|
172
|
+
pendingVerifications,
|
|
173
|
+
email: pendingVerifications.includes(constants_1.PENDING_VERIFICATION.email) ? user.email : undefined,
|
|
174
|
+
phoneNumber: pendingVerifications.includes(constants_1.PENDING_VERIFICATION.phone) ? user.phoneNumber : undefined,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
case constants_1.ACCOUNT_STATUS.deactivated: {
|
|
178
|
+
const reactivationToken = await this.challenges.createChallenge(constants_1.CHALLENGE_PURPOSE.reactivateAccount, user.id, signInMethod, constants_1.REACTIVATION_CHALLENGE_LIFETIME_SECONDS);
|
|
179
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.accountDeactivated, { reactivationToken, expiresIn: constants_1.REACTIVATION_CHALLENGE_LIFETIME_SECONDS });
|
|
180
|
+
}
|
|
181
|
+
case constants_1.ACCOUNT_STATUS.suspended:
|
|
182
|
+
await this.recordFailedSignIn(user.id, signInMethod, constants_1.SIGN_IN_FAILURE_REASON.accountSuspended, request);
|
|
183
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.accountSuspended);
|
|
184
|
+
case constants_1.ACCOUNT_STATUS.deleted:
|
|
185
|
+
default:
|
|
186
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidCredentials);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Starts the session, records the sign-in, and sends the "new sign-in" email for a new device.
|
|
191
|
+
*
|
|
192
|
+
* @param user - The user signing in.
|
|
193
|
+
* @param signInMethod - How they signed in.
|
|
194
|
+
* @param usedTwoFactor - Whether a second factor was used (recorded in the session and tokens).
|
|
195
|
+
* @param request - Who is asking.
|
|
196
|
+
* @param settings - The current settings.
|
|
197
|
+
* @returns Tokens plus the user's public profile.
|
|
198
|
+
*/
|
|
199
|
+
async startSignedInSession(user, signInMethod, usedTwoFactor, request, settings) {
|
|
200
|
+
const isNewDevice = await this.isNewDeviceForUser(user.id, request);
|
|
201
|
+
const tokens = await this.sessions.startSession(user, signInMethod, usedTwoFactor, request, settings);
|
|
202
|
+
await this.users.recordSignIn(user);
|
|
203
|
+
await this.events.record({
|
|
204
|
+
name: constants_1.AUTH_EVENTS.userSignedIn,
|
|
205
|
+
userId: user.id,
|
|
206
|
+
request,
|
|
207
|
+
details: { signInMethod, usedTwoFactor, sessionId: tokens.sessionId, isNewDevice },
|
|
208
|
+
});
|
|
209
|
+
if (isNewDevice && settings.security.notifyOnNewSignIn && user.email) {
|
|
210
|
+
await this.notifications.sendNoticeEmail({
|
|
211
|
+
type: constants_1.EMAIL_TYPE.newSignIn,
|
|
212
|
+
to: user.email,
|
|
213
|
+
ipAddress: request.ipAddress,
|
|
214
|
+
userAgent: request.userAgent,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return { success: true, status: constants_1.SIGN_IN_STATUS.signedIn, ...tokens, user: this.users.toPublicProfile(user, settings) };
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* "New device" = the user has signed in before, but never with this browser/app (user agent).
|
|
221
|
+
* The very first sign-in is not reported, since the user just created the account.
|
|
222
|
+
*
|
|
223
|
+
* @param userId - The user signing in.
|
|
224
|
+
* @param request - Holds the user agent.
|
|
225
|
+
* @returns True when the "new sign-in" email should be sent.
|
|
226
|
+
*/
|
|
227
|
+
async isNewDeviceForUser(userId, request) {
|
|
228
|
+
const sessionRepository = this.database.getRepository(entities_1.SessionEntity);
|
|
229
|
+
const hasSignedInBefore = await sessionRepository.exists({ where: { userId } });
|
|
230
|
+
if (!hasSignedInBefore)
|
|
231
|
+
return false;
|
|
232
|
+
if (!request.userAgent)
|
|
233
|
+
return true;
|
|
234
|
+
const hasUsedThisDevice = await sessionRepository.exists({ where: { userId, userAgent: request.userAgent } });
|
|
235
|
+
return !hasUsedThisDevice;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
exports.SignInService = SignInService;
|
|
239
|
+
exports.SignInService = SignInService = __decorate([
|
|
240
|
+
(0, common_1.Injectable)(),
|
|
241
|
+
__param(0, (0, common_1.Inject)(constants_1.AUTH_OPTIONS_TOKEN)),
|
|
242
|
+
__metadata("design:paramtypes", [Object, users_service_1.UsersService,
|
|
243
|
+
session_service_1.SessionService,
|
|
244
|
+
sign_in_challenges_service_1.SignInChallengesService,
|
|
245
|
+
notifications_service_1.NotificationsService,
|
|
246
|
+
auth_events_service_1.AuthEventsService])
|
|
247
|
+
], SignInService);
|
|
248
|
+
//# sourceMappingURL=sign-in.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign-in.service.js","sourceRoot":"","sources":["../../../src/features/sign-in/sign-in.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { TokenSet } from '../sessions/session.types.js';
|
|
2
|
+
import type { PublicUserProfile } from '../users/users.types.js';
|
|
3
|
+
import { type SIGN_IN_STATUS } from '../../constants.js';
|
|
4
|
+
/** The user is signed in. */
|
|
5
|
+
export interface SignedInResult extends TokenSet {
|
|
6
|
+
success: true;
|
|
7
|
+
status: typeof SIGN_IN_STATUS.signedIn;
|
|
8
|
+
user: PublicUserProfile;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The first step passed but the user has two-factor authentication on.
|
|
12
|
+
* Send POST /auth/2fa/verify with this challengeToken and the code from their authenticator app.
|
|
13
|
+
*/
|
|
14
|
+
export interface TwoFactorRequiredResult {
|
|
15
|
+
success: true;
|
|
16
|
+
status: typeof SIGN_IN_STATUS.twoFactorRequired;
|
|
17
|
+
challengeToken: string;
|
|
18
|
+
/** Seconds the user has to enter the code. */
|
|
19
|
+
expiresIn: number;
|
|
20
|
+
}
|
|
21
|
+
/** What every sign-in endpoint returns. */
|
|
22
|
+
export type SignInResult = SignedInResult | TwoFactorRequiredResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign-in.types.js","sourceRoot":"","sources":["../../../src/features/sign-in/sign-in.types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SigningKeysService } from './signing-keys.service';
|
|
2
|
+
/**
|
|
3
|
+
* Publishes the public keys that verify access tokens. Your other services (any language) can check
|
|
4
|
+
* tokens with a standard JWT library pointed at GET /.well-known/jwks.json.
|
|
5
|
+
*/
|
|
6
|
+
export declare class JwksController {
|
|
7
|
+
private readonly signingKeys;
|
|
8
|
+
constructor(signingKeys: SigningKeysService);
|
|
9
|
+
/**
|
|
10
|
+
* GET /.well-known/jwks.json — The public keys that verify access tokens. Public; may be cached for 5 minutes.
|
|
11
|
+
*
|
|
12
|
+
* @returns `{ keys: [...] }` in standard JWKS format.
|
|
13
|
+
*/
|
|
14
|
+
getPublicKeys(): Promise<{
|
|
15
|
+
keys: import("../../common/crypto/jwt").JsonWebKey[];
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.JwksController = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const auth_decorators_1 = require("../../guards/auth.decorators.js");
|
|
15
|
+
const signing_keys_service_1 = require("./signing-keys.service");
|
|
16
|
+
const constants_1 = require("../../constants.js");
|
|
17
|
+
const refresh_token_cookie_1 = require("../../common/refresh-token-cookie.js");
|
|
18
|
+
/**
|
|
19
|
+
* Publishes the public keys that verify access tokens. Your other services (any language) can check
|
|
20
|
+
* tokens with a standard JWT library pointed at GET /.well-known/jwks.json.
|
|
21
|
+
*/
|
|
22
|
+
let JwksController = class JwksController {
|
|
23
|
+
signingKeys;
|
|
24
|
+
constructor(signingKeys) {
|
|
25
|
+
this.signingKeys = signingKeys;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* GET /.well-known/jwks.json — The public keys that verify access tokens. Public; may be cached for 5 minutes.
|
|
29
|
+
*
|
|
30
|
+
* @returns `{ keys: [...] }` in standard JWKS format.
|
|
31
|
+
*/
|
|
32
|
+
async getPublicKeys() {
|
|
33
|
+
const { publicJwks } = await this.signingKeys.getSigningKeys();
|
|
34
|
+
return { keys: publicJwks };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
exports.JwksController = JwksController;
|
|
38
|
+
__decorate([
|
|
39
|
+
(0, common_1.Get)(constants_1.ROUTES.wellKnown.jwks),
|
|
40
|
+
(0, common_1.Header)(constants_1.HTTP_HEADER.cacheControl, constants_1.JWKS_CACHE_CONTROL),
|
|
41
|
+
__metadata("design:type", Function),
|
|
42
|
+
__metadata("design:paramtypes", []),
|
|
43
|
+
__metadata("design:returntype", Promise)
|
|
44
|
+
], JwksController.prototype, "getPublicKeys", null);
|
|
45
|
+
exports.JwksController = JwksController = __decorate([
|
|
46
|
+
(0, common_1.Controller)(constants_1.ROUTES.wellKnown.prefix),
|
|
47
|
+
(0, refresh_token_cookie_1.LibraryController)(),
|
|
48
|
+
(0, auth_decorators_1.Public)(),
|
|
49
|
+
__metadata("design:paramtypes", [signing_keys_service_1.SigningKeysService])
|
|
50
|
+
], JwksController);
|
|
51
|
+
//# sourceMappingURL=jwks.controller.js.map
|