@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,117 @@
|
|
|
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.AuthEventsService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const constants_1 = require("../../constants.js");
|
|
18
|
+
const entities_1 = require("../../database/entities/index.js");
|
|
19
|
+
const typeorm_row_1 = require("../../database/typeorm-row.js");
|
|
20
|
+
/**
|
|
21
|
+
* The single place every auth event goes through. One call writes the audit log, then calls
|
|
22
|
+
* your `onEvent` handler, then queues a delivery for every webhook subscribed to the event.
|
|
23
|
+
*
|
|
24
|
+
* Recording an event never breaks the action that caused it: failures here are logged, not thrown.
|
|
25
|
+
*/
|
|
26
|
+
let AuthEventsService = class AuthEventsService {
|
|
27
|
+
options;
|
|
28
|
+
logger = new common_1.Logger(constants_1.LOG_CONTEXT.events);
|
|
29
|
+
database;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.options = options;
|
|
32
|
+
this.database = options.dataSource;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Records an event: writes the audit log, calls your `onEvent` handler, and queues webhook deliveries.
|
|
36
|
+
* Never throws, so recording an event can't break the action that caused it.
|
|
37
|
+
*
|
|
38
|
+
* @param input - The event name, who it is about, who did it, the request, and extra details.
|
|
39
|
+
* @example
|
|
40
|
+
* await events.record({ name: AUTH_EVENTS.userSignedOut, userId, request, details: { sessionId } });
|
|
41
|
+
*/
|
|
42
|
+
async record(input) {
|
|
43
|
+
const event = {
|
|
44
|
+
name: input.name,
|
|
45
|
+
userId: input.userId,
|
|
46
|
+
data: input.details ?? {},
|
|
47
|
+
occurredAt: new Date().toISOString(),
|
|
48
|
+
};
|
|
49
|
+
await this.writeAuditLog(input);
|
|
50
|
+
await this.callAppHandler(event);
|
|
51
|
+
await this.queueWebhookDeliveries(event);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* @param input - The event to write. Failures are logged, not thrown.
|
|
55
|
+
*/
|
|
56
|
+
async writeAuditLog(input) {
|
|
57
|
+
try {
|
|
58
|
+
await this.database.getRepository(entities_1.AuditLogEntity).insert((0, typeorm_row_1.typeormRow)({
|
|
59
|
+
eventName: input.name,
|
|
60
|
+
userId: input.userId,
|
|
61
|
+
performedBy: input.performedBy ?? null,
|
|
62
|
+
ipAddress: input.request?.ipAddress ?? null,
|
|
63
|
+
userAgent: input.request?.userAgent ?? null,
|
|
64
|
+
details: input.details ?? {},
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
this.logger.error(`Could not write audit log for "${input.name}": ${error.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Calls AuthOptions.onEvent, if set. Failures in your handler are logged, not thrown.
|
|
73
|
+
*
|
|
74
|
+
* @param event - The event to pass on.
|
|
75
|
+
*/
|
|
76
|
+
async callAppHandler(event) {
|
|
77
|
+
if (!this.options.onEvent)
|
|
78
|
+
return;
|
|
79
|
+
try {
|
|
80
|
+
await this.options.onEvent(event);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
this.logger.error(`Your onEvent handler failed for "${event.name}": ${error.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Queues one delivery per enabled webhook that subscribes to this event. The worker sends them.
|
|
88
|
+
* Failures are logged, not thrown.
|
|
89
|
+
*
|
|
90
|
+
* @param event - The event to deliver.
|
|
91
|
+
*/
|
|
92
|
+
async queueWebhookDeliveries(event) {
|
|
93
|
+
try {
|
|
94
|
+
const enabledWebhooks = await this.database.getRepository(entities_1.WebhookEntity).find({ where: { isEnabled: true } });
|
|
95
|
+
const subscribedWebhooks = enabledWebhooks.filter((webhook) => webhook.eventNames.length === 0 || webhook.eventNames.includes(event.name));
|
|
96
|
+
if (subscribedWebhooks.length === 0)
|
|
97
|
+
return;
|
|
98
|
+
await this.database.getRepository(entities_1.WebhookDeliveryEntity).insert((0, typeorm_row_1.typeormRows)(subscribedWebhooks.map((webhook) => ({
|
|
99
|
+
webhookId: webhook.id,
|
|
100
|
+
eventName: event.name,
|
|
101
|
+
payload: { ...event },
|
|
102
|
+
status: constants_1.WEBHOOK_DELIVERY_STATUS.pending,
|
|
103
|
+
nextAttemptAt: new Date(),
|
|
104
|
+
}))));
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
this.logger.error(`Could not queue webhooks for "${event.name}": ${error.message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
exports.AuthEventsService = AuthEventsService;
|
|
112
|
+
exports.AuthEventsService = AuthEventsService = __decorate([
|
|
113
|
+
(0, common_1.Injectable)(),
|
|
114
|
+
__param(0, (0, common_1.Inject)(constants_1.AUTH_OPTIONS_TOKEN)),
|
|
115
|
+
__metadata("design:paramtypes", [Object])
|
|
116
|
+
], AuthEventsService);
|
|
117
|
+
//# sourceMappingURL=auth-events.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-events.service.js","sourceRoot":"","sources":["../../../src/features/events/auth-events.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import { AuthOptions } from '../../auth-options.js';
|
|
3
|
+
import { DataRetentionStep } from '../../constants.js';
|
|
4
|
+
import { SettingsService } from '../settings/settings.service.js';
|
|
5
|
+
/** How many rows each cleanup step removed. */
|
|
6
|
+
export type DataRetentionReport = Record<DataRetentionStep, number>;
|
|
7
|
+
/**
|
|
8
|
+
* Deletes rows nobody needs any more, so tables don't grow forever:
|
|
9
|
+
*
|
|
10
|
+
* | What | Kept for (settings.retention) |
|
|
11
|
+
* |--------------------------------------------------|-------------------------------|
|
|
12
|
+
* | ended sessions (+ their refresh tokens) | expiredRecordDays (7) |
|
|
13
|
+
* | expired refresh tokens of active sessions | expiredRecordDays |
|
|
14
|
+
* | used/expired email links, codes, 2FA challenges | expiredRecordDays |
|
|
15
|
+
* | accepted/cancelled/expired invitations | expiredRecordDays |
|
|
16
|
+
* | delivered or failed webhook deliveries | webhookDeliveryDays (30) |
|
|
17
|
+
* | audit log entries | auditLogDays (365, 0=forever) |
|
|
18
|
+
*
|
|
19
|
+
* Runs every hour on every server. Several servers running it at once is harmless: each deletes
|
|
20
|
+
* in small batches, and a row already deleted by one is simply not found by the other.
|
|
21
|
+
* Users, organizations, passkeys and other live data are never touched.
|
|
22
|
+
*/
|
|
23
|
+
export declare class DataRetentionService implements OnApplicationBootstrap, OnModuleDestroy {
|
|
24
|
+
private readonly settingsService;
|
|
25
|
+
private readonly logger;
|
|
26
|
+
private readonly database;
|
|
27
|
+
private firstRunTimer?;
|
|
28
|
+
private intervalTimer?;
|
|
29
|
+
private isRunning;
|
|
30
|
+
private readonly steps;
|
|
31
|
+
constructor(options: AuthOptions, settingsService: SettingsService);
|
|
32
|
+
/**
|
|
33
|
+
* Schedules the first cleanup shortly after start-up, then one every hour.
|
|
34
|
+
*/
|
|
35
|
+
onApplicationBootstrap(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Stops the timers when the app shuts down.
|
|
38
|
+
*/
|
|
39
|
+
onModuleDestroy(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Runs every cleanup step now. Works even when the automatic cleanup is turned off,
|
|
42
|
+
* so you can call it from your own scheduled job.
|
|
43
|
+
*
|
|
44
|
+
* @returns How many rows each step removed.
|
|
45
|
+
*/
|
|
46
|
+
runCleanup(): Promise<DataRetentionReport>;
|
|
47
|
+
/**
|
|
48
|
+
* The hourly run: skipped when retention is turned off or a run is still going.
|
|
49
|
+
* Errors are logged, never thrown.
|
|
50
|
+
*/
|
|
51
|
+
private runScheduledCleanup;
|
|
52
|
+
/**
|
|
53
|
+
* Deletes matching rows in batches (short statements, short locks).
|
|
54
|
+
*
|
|
55
|
+
* @param step - Which table and which rows count as old.
|
|
56
|
+
* @param cutoff - Rows older than this are deleted.
|
|
57
|
+
* @returns How many rows were deleted.
|
|
58
|
+
*/
|
|
59
|
+
private deleteOldRows;
|
|
60
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
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.DataRetentionService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const constants_1 = require("../../constants.js");
|
|
18
|
+
const entities_1 = require("../../database/entities/index.js");
|
|
19
|
+
const settings_service_1 = require("../settings/settings.service.js");
|
|
20
|
+
/**
|
|
21
|
+
* Deletes rows nobody needs any more, so tables don't grow forever:
|
|
22
|
+
*
|
|
23
|
+
* | What | Kept for (settings.retention) |
|
|
24
|
+
* |--------------------------------------------------|-------------------------------|
|
|
25
|
+
* | ended sessions (+ their refresh tokens) | expiredRecordDays (7) |
|
|
26
|
+
* | expired refresh tokens of active sessions | expiredRecordDays |
|
|
27
|
+
* | used/expired email links, codes, 2FA challenges | expiredRecordDays |
|
|
28
|
+
* | accepted/cancelled/expired invitations | expiredRecordDays |
|
|
29
|
+
* | delivered or failed webhook deliveries | webhookDeliveryDays (30) |
|
|
30
|
+
* | audit log entries | auditLogDays (365, 0=forever) |
|
|
31
|
+
*
|
|
32
|
+
* Runs every hour on every server. Several servers running it at once is harmless: each deletes
|
|
33
|
+
* in small batches, and a row already deleted by one is simply not found by the other.
|
|
34
|
+
* Users, organizations, passkeys and other live data are never touched.
|
|
35
|
+
*/
|
|
36
|
+
let DataRetentionService = class DataRetentionService {
|
|
37
|
+
settingsService;
|
|
38
|
+
logger = new common_1.Logger(constants_1.LOG_CONTEXT.dataRetention);
|
|
39
|
+
database;
|
|
40
|
+
firstRunTimer;
|
|
41
|
+
intervalTimer;
|
|
42
|
+
isRunning = false;
|
|
43
|
+
steps = [
|
|
44
|
+
{
|
|
45
|
+
step: constants_1.DATA_RETENTION_STEP.endedSessions,
|
|
46
|
+
entity: entities_1.SessionEntity,
|
|
47
|
+
keepDays: (settings) => settings.retention.expiredRecordDays,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
step: constants_1.DATA_RETENTION_STEP.expiredRefreshTokens,
|
|
51
|
+
entity: entities_1.RefreshTokenEntity,
|
|
52
|
+
keepDays: (settings) => settings.retention.expiredRecordDays,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
step: constants_1.DATA_RETENTION_STEP.linkTokens,
|
|
56
|
+
entity: entities_1.LinkTokenEntity,
|
|
57
|
+
keepDays: (settings) => settings.retention.expiredRecordDays,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
step: constants_1.DATA_RETENTION_STEP.oneTimeCodes,
|
|
61
|
+
entity: entities_1.OneTimeCodeEntity,
|
|
62
|
+
keepDays: (settings) => settings.retention.expiredRecordDays,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
step: constants_1.DATA_RETENTION_STEP.signInChallenges,
|
|
66
|
+
entity: entities_1.SignInChallengeEntity,
|
|
67
|
+
keepDays: (settings) => settings.retention.expiredRecordDays,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
step: constants_1.DATA_RETENTION_STEP.organizationInvitations,
|
|
71
|
+
entity: entities_1.OrganizationInvitationEntity,
|
|
72
|
+
keepDays: (settings) => settings.retention.expiredRecordDays,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
step: constants_1.DATA_RETENTION_STEP.webhookDeliveries,
|
|
76
|
+
entity: entities_1.WebhookDeliveryEntity,
|
|
77
|
+
keepDays: (settings) => settings.retention.webhookDeliveryDays,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
step: constants_1.DATA_RETENTION_STEP.auditLog,
|
|
81
|
+
entity: entities_1.AuditLogEntity,
|
|
82
|
+
keepDays: (settings) => settings.retention.auditLogDays,
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
constructor(options, settingsService) {
|
|
86
|
+
this.settingsService = settingsService;
|
|
87
|
+
this.database = options.dataSource;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Schedules the first cleanup shortly after start-up, then one every hour.
|
|
91
|
+
*/
|
|
92
|
+
onApplicationBootstrap() {
|
|
93
|
+
this.firstRunTimer = setTimeout(() => void this.runScheduledCleanup(), constants_1.DATA_RETENTION_FIRST_RUN_DELAY_MS);
|
|
94
|
+
this.intervalTimer = setInterval(() => void this.runScheduledCleanup(), constants_1.DATA_RETENTION_INTERVAL_MS);
|
|
95
|
+
// Timers never keep the process alive on shutdown.
|
|
96
|
+
this.firstRunTimer.unref();
|
|
97
|
+
this.intervalTimer.unref();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Stops the timers when the app shuts down.
|
|
101
|
+
*/
|
|
102
|
+
onModuleDestroy() {
|
|
103
|
+
if (this.firstRunTimer)
|
|
104
|
+
clearTimeout(this.firstRunTimer);
|
|
105
|
+
if (this.intervalTimer)
|
|
106
|
+
clearInterval(this.intervalTimer);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Runs every cleanup step now. Works even when the automatic cleanup is turned off,
|
|
110
|
+
* so you can call it from your own scheduled job.
|
|
111
|
+
*
|
|
112
|
+
* @returns How many rows each step removed.
|
|
113
|
+
*/
|
|
114
|
+
async runCleanup() {
|
|
115
|
+
const settings = await this.settingsService.getSettings();
|
|
116
|
+
const report = Object.fromEntries(this.steps.map(({ step }) => [step, 0]));
|
|
117
|
+
for (const step of this.steps) {
|
|
118
|
+
const keepDays = step.keepDays(settings);
|
|
119
|
+
if (keepDays <= 0)
|
|
120
|
+
continue;
|
|
121
|
+
report[step.step] = await this.deleteOldRows(step, new Date(Date.now() - keepDays * constants_1.DAY_IN_SECONDS * constants_1.SECOND_IN_MILLISECONDS));
|
|
122
|
+
}
|
|
123
|
+
return report;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The hourly run: skipped when retention is turned off or a run is still going.
|
|
127
|
+
* Errors are logged, never thrown.
|
|
128
|
+
*/
|
|
129
|
+
async runScheduledCleanup() {
|
|
130
|
+
if (this.isRunning)
|
|
131
|
+
return;
|
|
132
|
+
this.isRunning = true;
|
|
133
|
+
try {
|
|
134
|
+
if (!(await this.settingsService.getSettings()).retention.enabled)
|
|
135
|
+
return;
|
|
136
|
+
const report = await this.runCleanup();
|
|
137
|
+
const removedRows = Object.values(report).reduce((total, count) => total + count, 0);
|
|
138
|
+
if (removedRows > 0)
|
|
139
|
+
this.logger.log(`Removed ${removedRows} old rows: ${JSON.stringify(report)}`);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
this.logger.error(`Cleanup failed: ${error.message}`);
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
this.isRunning = false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Deletes matching rows in batches (short statements, short locks).
|
|
150
|
+
*
|
|
151
|
+
* @param step - Which table and which rows count as old.
|
|
152
|
+
* @param cutoff - Rows older than this are deleted.
|
|
153
|
+
* @returns How many rows were deleted.
|
|
154
|
+
*/
|
|
155
|
+
async deleteOldRows(step, cutoff) {
|
|
156
|
+
const tableName = this.database.getMetadata(step.entity).tableName;
|
|
157
|
+
let removedRows = 0;
|
|
158
|
+
for (let batch = 0; batch < constants_1.DATA_RETENTION_MAX_BATCHES_PER_TABLE; batch++) {
|
|
159
|
+
const result = await this.database
|
|
160
|
+
.createQueryBuilder()
|
|
161
|
+
.delete()
|
|
162
|
+
.from(step.entity)
|
|
163
|
+
.where((0, constants_1.DATA_RETENTION_BATCH_CONDITION)(tableName, constants_1.DATA_RETENTION_OLD_ROW_CONDITION[step.step]), {
|
|
164
|
+
cutoff,
|
|
165
|
+
batchSize: constants_1.DATA_RETENTION_BATCH_SIZE,
|
|
166
|
+
})
|
|
167
|
+
.execute();
|
|
168
|
+
const deletedInBatch = result.affected ?? 0;
|
|
169
|
+
removedRows += deletedInBatch;
|
|
170
|
+
if (deletedInBatch < constants_1.DATA_RETENTION_BATCH_SIZE)
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
return removedRows;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
exports.DataRetentionService = DataRetentionService;
|
|
177
|
+
exports.DataRetentionService = DataRetentionService = __decorate([
|
|
178
|
+
(0, common_1.Injectable)(),
|
|
179
|
+
__param(0, (0, common_1.Inject)(constants_1.AUTH_OPTIONS_TOKEN)),
|
|
180
|
+
__metadata("design:paramtypes", [Object, settings_service_1.SettingsService])
|
|
181
|
+
], DataRetentionService);
|
|
182
|
+
//# sourceMappingURL=data-retention.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-retention.service.js","sourceRoot":"","sources":["../../../src/features/maintenance/data-retention.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { AuthEmail, AuthOptions } from '../../auth-options.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sends emails and SMS through the senders your app provides in AuthOptions.
|
|
4
|
+
*
|
|
5
|
+
* Two kinds of message:
|
|
6
|
+
* - Required (a code or link the user is waiting for): if sending fails, the request fails, so the
|
|
7
|
+
* user knows to try again.
|
|
8
|
+
* - Notices (password changed, new sign-in): sending failures are logged and the action still succeeds.
|
|
9
|
+
*/
|
|
10
|
+
export declare class NotificationsService {
|
|
11
|
+
private readonly options;
|
|
12
|
+
private readonly logger;
|
|
13
|
+
constructor(options: AuthOptions);
|
|
14
|
+
/**
|
|
15
|
+
* @returns True when an SMS sender is configured (phone OTP needs one).
|
|
16
|
+
*/
|
|
17
|
+
get canSendSms(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Sends an email the user is waiting for (a code or link).
|
|
20
|
+
*
|
|
21
|
+
* @param email - The email to send; its `type` says which template your sender should use.
|
|
22
|
+
* @throws AuthError INTERNAL_SERVER_ERROR if it could not be sent, so the user knows to try again.
|
|
23
|
+
*/
|
|
24
|
+
sendRequiredEmail(email: AuthEmail): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Sends an informational email (password changed, new sign-in). Never throws; failures are logged.
|
|
27
|
+
*
|
|
28
|
+
* @param email - The email to send.
|
|
29
|
+
*/
|
|
30
|
+
sendNoticeEmail(email: AuthEmail): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Sends a one-time code by SMS.
|
|
33
|
+
*
|
|
34
|
+
* @param phoneNumber - The E.164 phone number.
|
|
35
|
+
* @param code - The code to send.
|
|
36
|
+
* @throws AuthError NOT_CONFIGURED when no SMS sender is configured.
|
|
37
|
+
* @throws AuthError INTERNAL_SERVER_ERROR when sending failed.
|
|
38
|
+
*/
|
|
39
|
+
sendSmsCode(phoneNumber: string, code: string): Promise<void>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
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.NotificationsService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const auth_error_1 = require("../../common/auth-error.js");
|
|
18
|
+
const identifiers_1 = require("../../common/identifiers.js");
|
|
19
|
+
const constants_1 = require("../../constants.js");
|
|
20
|
+
/**
|
|
21
|
+
* Sends emails and SMS through the senders your app provides in AuthOptions.
|
|
22
|
+
*
|
|
23
|
+
* Two kinds of message:
|
|
24
|
+
* - Required (a code or link the user is waiting for): if sending fails, the request fails, so the
|
|
25
|
+
* user knows to try again.
|
|
26
|
+
* - Notices (password changed, new sign-in): sending failures are logged and the action still succeeds.
|
|
27
|
+
*/
|
|
28
|
+
let NotificationsService = class NotificationsService {
|
|
29
|
+
options;
|
|
30
|
+
logger = new common_1.Logger(constants_1.LOG_CONTEXT.notifications);
|
|
31
|
+
constructor(options) {
|
|
32
|
+
this.options = options;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* @returns True when an SMS sender is configured (phone OTP needs one).
|
|
36
|
+
*/
|
|
37
|
+
get canSendSms() {
|
|
38
|
+
return this.options.smsSender !== undefined;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Sends an email the user is waiting for (a code or link).
|
|
42
|
+
*
|
|
43
|
+
* @param email - The email to send; its `type` says which template your sender should use.
|
|
44
|
+
* @throws AuthError INTERNAL_SERVER_ERROR if it could not be sent, so the user knows to try again.
|
|
45
|
+
*/
|
|
46
|
+
async sendRequiredEmail(email) {
|
|
47
|
+
try {
|
|
48
|
+
await this.options.emailSender.send(email);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
this.logger.error(`Could not send "${email.type}" email: ${error.message}`);
|
|
52
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.internalError);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Sends an informational email (password changed, new sign-in). Never throws; failures are logged.
|
|
57
|
+
*
|
|
58
|
+
* @param email - The email to send.
|
|
59
|
+
*/
|
|
60
|
+
async sendNoticeEmail(email) {
|
|
61
|
+
try {
|
|
62
|
+
await this.options.emailSender.send(email);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
this.logger.warn(`Could not send "${email.type}" notice email: ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Sends a one-time code by SMS.
|
|
70
|
+
*
|
|
71
|
+
* @param phoneNumber - The E.164 phone number.
|
|
72
|
+
* @param code - The code to send.
|
|
73
|
+
* @throws AuthError NOT_CONFIGURED when no SMS sender is configured.
|
|
74
|
+
* @throws AuthError INTERNAL_SERVER_ERROR when sending failed.
|
|
75
|
+
*/
|
|
76
|
+
async sendSmsCode(phoneNumber, code) {
|
|
77
|
+
if (!this.options.smsSender)
|
|
78
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.notConfigured);
|
|
79
|
+
try {
|
|
80
|
+
await this.options.smsSender.sendCode(phoneNumber, code);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
this.logger.error(`Could not send SMS to ${(0, identifiers_1.maskPhoneNumber)(phoneNumber)}: ${error.message}`);
|
|
84
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.internalError);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
exports.NotificationsService = NotificationsService;
|
|
89
|
+
exports.NotificationsService = NotificationsService = __decorate([
|
|
90
|
+
(0, common_1.Injectable)(),
|
|
91
|
+
__param(0, (0, common_1.Inject)(constants_1.AUTH_OPTIONS_TOKEN)),
|
|
92
|
+
__metadata("design:paramtypes", [Object])
|
|
93
|
+
], NotificationsService);
|
|
94
|
+
//# sourceMappingURL=notifications.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifications.service.js","sourceRoot":"","sources":["../../../src/features/notifications/notifications.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA4D;AAE5D,oDAAgD;AAChD,sDAAuD;AACvD,2CAA2E;AAE3E;;;;;;;GAOG;AAEI,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAG0B;IAFxC,MAAM,GAAG,IAAI,eAAM,CAAC,uBAAW,CAAC,aAAa,CAAC,CAAC;IAEhE,YAAyD,OAAoB;QAApB,YAAO,GAAP,OAAO,CAAa;IAAG,CAAC;IAEjF;;OAEG;IACH,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;IAC9C,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,iBAAiB,CAAC,KAAgB;QACtC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,KAAK,CAAC,IAAI,YAAa,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;YACvF,MAAM,IAAI,sBAAS,CAAC,uBAAW,CAAC,aAAa,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,eAAe,CAAC,KAAgB;QACpC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,KAAK,CAAC,IAAI,mBAAoB,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/F,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,WAAW,CAAC,WAAmB,EAAE,IAAY;QACjD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS;YAAE,MAAM,IAAI,sBAAS,CAAC,uBAAW,CAAC,aAAa,CAAC,CAAC;QAC5E,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QAC3D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,IAAA,6BAAe,EAAC,WAAW,CAAC,KAAM,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;YACxG,MAAM,IAAI,sBAAS,CAAC,uBAAW,CAAC,aAAa,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;CACF,CAAA;AAzDY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;IAIE,WAAA,IAAA,eAAM,EAAC,8BAAkB,CAAC,CAAA;;GAH5B,oBAAoB,CAyDhC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { AuthOptions } from '../../auth-options.js';
|
|
2
|
+
import { CodeChannel } from '../../database/entities/index.js';
|
|
3
|
+
export interface CodeRules {
|
|
4
|
+
codeLength: number;
|
|
5
|
+
expiresInMinutes: number;
|
|
6
|
+
resendAfterSeconds: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Short numeric codes sent by email or SMS ("Your code is 482913").
|
|
10
|
+
*
|
|
11
|
+
* - Only a salted hash of the code is stored.
|
|
12
|
+
* - A new code can be requested after `resendAfterSeconds` (default a minute); it replaces the previous one.
|
|
13
|
+
* - After ONE_TIME_CODE_MAX_ATTEMPTS wrong tries the code stops working.
|
|
14
|
+
* - A code works once.
|
|
15
|
+
*/
|
|
16
|
+
export declare class OneTimeCodesService {
|
|
17
|
+
private readonly database;
|
|
18
|
+
constructor(options: AuthOptions);
|
|
19
|
+
/**
|
|
20
|
+
* Creates a new code for `destination` and returns it so the caller can send it.
|
|
21
|
+
* Any earlier code for the same destination stops working.
|
|
22
|
+
*
|
|
23
|
+
* @param channel - "email" or "sms".
|
|
24
|
+
* @param destination - An email or E.164 phone number.
|
|
25
|
+
* @param rules - Code length, lifetime and how soon a new one may be sent.
|
|
26
|
+
* @returns The plain code to send. Only its hash is stored.
|
|
27
|
+
* @throws AuthError TOO_MANY_REQUESTS (with `retryAfterSeconds`) if a code was sent less than `resendAfterSeconds` ago.
|
|
28
|
+
*/
|
|
29
|
+
createCode(channel: CodeChannel, destination: string, rules: CodeRules): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Checks a typed code and uses it up if right.
|
|
32
|
+
*
|
|
33
|
+
* @param channel - "email" or "sms".
|
|
34
|
+
* @param destination - The email or phone number the code was sent to.
|
|
35
|
+
* @param typedCode - What the user typed.
|
|
36
|
+
* @throws AuthError OTP_EXPIRED when there is no usable code (none sent, expired, used, or too many wrong tries).
|
|
37
|
+
* @throws AuthError INVALID_OTP (with `attemptsLeft`) when the code is wrong.
|
|
38
|
+
*/
|
|
39
|
+
verifyCode(channel: CodeChannel, destination: string, typedCode: unknown): Promise<void>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
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.OneTimeCodesService = 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 numeric codes sent by email or SMS ("Your code is 482913").
|
|
24
|
+
*
|
|
25
|
+
* - Only a salted hash of the code is stored.
|
|
26
|
+
* - A new code can be requested after `resendAfterSeconds` (default a minute); it replaces the previous one.
|
|
27
|
+
* - After ONE_TIME_CODE_MAX_ATTEMPTS wrong tries the code stops working.
|
|
28
|
+
* - A code works once.
|
|
29
|
+
*/
|
|
30
|
+
let OneTimeCodesService = class OneTimeCodesService {
|
|
31
|
+
database;
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.database = options.dataSource;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Creates a new code for `destination` and returns it so the caller can send it.
|
|
37
|
+
* Any earlier code for the same destination stops working.
|
|
38
|
+
*
|
|
39
|
+
* @param channel - "email" or "sms".
|
|
40
|
+
* @param destination - An email or E.164 phone number.
|
|
41
|
+
* @param rules - Code length, lifetime and how soon a new one may be sent.
|
|
42
|
+
* @returns The plain code to send. Only its hash is stored.
|
|
43
|
+
* @throws AuthError TOO_MANY_REQUESTS (with `retryAfterSeconds`) if a code was sent less than `resendAfterSeconds` ago.
|
|
44
|
+
*/
|
|
45
|
+
async createCode(channel, destination, rules) {
|
|
46
|
+
const codes = this.database.getRepository(entities_1.OneTimeCodeEntity);
|
|
47
|
+
const resendAllowedAfter = new Date(Date.now() - rules.resendAfterSeconds * constants_1.SECOND_IN_MILLISECONDS);
|
|
48
|
+
const sentRecently = await codes.exists({ where: { channel, destination, createdAt: (0, typeorm_1.MoreThan)(resendAllowedAfter) } });
|
|
49
|
+
if (sentRecently)
|
|
50
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.resendTooSoon, { retryAfterSeconds: rules.resendAfterSeconds });
|
|
51
|
+
const code = (0, random_secrets_1.createNumericCode)(rules.codeLength);
|
|
52
|
+
const salt = (0, random_secrets_1.createSalt)();
|
|
53
|
+
await this.database.transaction(async (transaction) => {
|
|
54
|
+
// Only the newest code works.
|
|
55
|
+
await transaction.update(entities_1.OneTimeCodeEntity, { channel, destination, usedAt: (0, typeorm_1.IsNull)() }, { usedAt: new Date() });
|
|
56
|
+
await transaction.insert(entities_1.OneTimeCodeEntity, {
|
|
57
|
+
channel,
|
|
58
|
+
destination,
|
|
59
|
+
salt,
|
|
60
|
+
codeHash: (0, random_secrets_1.hashShortCode)(code, salt),
|
|
61
|
+
expiresAt: new Date(Date.now() + rules.expiresInMinutes * constants_1.MINUTE_IN_SECONDS * constants_1.SECOND_IN_MILLISECONDS),
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
return code;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Checks a typed code and uses it up if right.
|
|
68
|
+
*
|
|
69
|
+
* @param channel - "email" or "sms".
|
|
70
|
+
* @param destination - The email or phone number the code was sent to.
|
|
71
|
+
* @param typedCode - What the user typed.
|
|
72
|
+
* @throws AuthError OTP_EXPIRED when there is no usable code (none sent, expired, used, or too many wrong tries).
|
|
73
|
+
* @throws AuthError INVALID_OTP (with `attemptsLeft`) when the code is wrong.
|
|
74
|
+
*/
|
|
75
|
+
async verifyCode(channel, destination, typedCode) {
|
|
76
|
+
const codes = this.database.getRepository(entities_1.OneTimeCodeEntity);
|
|
77
|
+
const latestCode = await codes.findOne({
|
|
78
|
+
where: { channel, destination, usedAt: (0, typeorm_1.IsNull)() },
|
|
79
|
+
order: { createdAt: constants_1.SORT_ORDER.newestFirst },
|
|
80
|
+
});
|
|
81
|
+
const isUsable = latestCode && latestCode.expiresAt.getTime() > Date.now() && latestCode.failedAttempts < constants_1.ONE_TIME_CODE_MAX_ATTEMPTS;
|
|
82
|
+
if (!isUsable)
|
|
83
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.otpExpired);
|
|
84
|
+
const codeIsRight = typeof typedCode === 'string' && (0, random_secrets_1.constantTimeEquals)((0, random_secrets_1.hashShortCode)(typedCode.trim(), latestCode.salt), latestCode.codeHash);
|
|
85
|
+
if (!codeIsRight) {
|
|
86
|
+
await codes.increment({ id: latestCode.id }, 'failedAttempts', 1);
|
|
87
|
+
const attemptsLeft = constants_1.ONE_TIME_CODE_MAX_ATTEMPTS - latestCode.failedAttempts - 1;
|
|
88
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidOtp, { attemptsLeft: Math.max(attemptsLeft, 0) });
|
|
89
|
+
}
|
|
90
|
+
// Use it up in one UPDATE, so the same code can't sign in twice at the same moment.
|
|
91
|
+
const result = await codes.update({ id: latestCode.id, usedAt: (0, typeorm_1.IsNull)() }, { usedAt: new Date() });
|
|
92
|
+
if (!result.affected)
|
|
93
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.otpExpired);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
exports.OneTimeCodesService = OneTimeCodesService;
|
|
97
|
+
exports.OneTimeCodesService = OneTimeCodesService = __decorate([
|
|
98
|
+
(0, common_1.Injectable)(),
|
|
99
|
+
__param(0, (0, common_1.Inject)(constants_1.AUTH_OPTIONS_TOKEN)),
|
|
100
|
+
__metadata("design:paramtypes", [Object])
|
|
101
|
+
], OneTimeCodesService);
|
|
102
|
+
//# sourceMappingURL=one-time-codes.service.js.map
|