@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,238 @@
|
|
|
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.WebhooksService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const node_crypto_1 = require("node:crypto");
|
|
18
|
+
const auth_error_1 = require("../../common/auth-error.js");
|
|
19
|
+
const encryption_service_1 = require("../../common/encryption.service.js");
|
|
20
|
+
const network_safety_1 = require("../../common/network-safety.js");
|
|
21
|
+
const constants_1 = require("../../constants.js");
|
|
22
|
+
const entities_1 = require("../../database/entities/index.js");
|
|
23
|
+
const typeorm_row_1 = require("../../database/typeorm-row.js");
|
|
24
|
+
const KNOWN_EVENT_NAMES = new Set(Object.values(constants_1.AUTH_EVENTS));
|
|
25
|
+
/**
|
|
26
|
+
* Admin management of webhooks: URLs that receive auth events as signed HTTP POSTs.
|
|
27
|
+
* Deliveries themselves are sent by WebhookDeliveryWorker.
|
|
28
|
+
*/
|
|
29
|
+
let WebhooksService = class WebhooksService {
|
|
30
|
+
encryption;
|
|
31
|
+
database;
|
|
32
|
+
constructor(options, encryption) {
|
|
33
|
+
this.encryption = encryption;
|
|
34
|
+
this.database = options.dataSource;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Creates a webhook. Returns the signing secret ONCE: the receiver needs it to verify deliveries.
|
|
38
|
+
*
|
|
39
|
+
* @param input - URL, event names (empty = every event), description and on/off state.
|
|
40
|
+
* @returns The webhook and its plain signing secret.
|
|
41
|
+
* @throws AuthError UNSAFE_WEBHOOK_URL when the URL is not https or points at a private address.
|
|
42
|
+
* @throws AuthError VALIDATION_ERROR for unknown event names or too many webhooks.
|
|
43
|
+
*/
|
|
44
|
+
async createWebhook(input) {
|
|
45
|
+
const webhooks = this.database.getRepository(entities_1.WebhookEntity);
|
|
46
|
+
if ((await webhooks.count()) >= constants_1.MAXIMUM_WEBHOOKS)
|
|
47
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.validationFailed, { problems: [constants_1.VALIDATION_PROBLEM.tooManyWebhooks] });
|
|
48
|
+
await this.assertUrlIsSafe(input.url);
|
|
49
|
+
const eventNames = this.validateEventNames(input.eventNames ?? []);
|
|
50
|
+
const webhookId = (0, node_crypto_1.randomUUID)();
|
|
51
|
+
const signingSecret = this.createSigningSecret();
|
|
52
|
+
const savedWebhook = await webhooks.save({
|
|
53
|
+
id: webhookId,
|
|
54
|
+
url: input.url,
|
|
55
|
+
eventNames,
|
|
56
|
+
description: input.description ?? null,
|
|
57
|
+
isEnabled: input.isEnabled ?? true,
|
|
58
|
+
encryptedSigningSecret: await this.encryption.encrypt(signingSecret, this.encryptionContext(webhookId)),
|
|
59
|
+
});
|
|
60
|
+
return { success: true, webhook: this.toSummary(savedWebhook), signingSecret };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Lists every webhook, oldest first.
|
|
64
|
+
*
|
|
65
|
+
* @returns The webhooks, without their secrets.
|
|
66
|
+
*/
|
|
67
|
+
async listWebhooks() {
|
|
68
|
+
const webhooks = await this.database.getRepository(entities_1.WebhookEntity).find({ order: { createdAt: constants_1.SORT_ORDER.oldestFirst } });
|
|
69
|
+
return webhooks.map((webhook) => this.toSummary(webhook));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Changes a webhook. Fields left undefined are not touched.
|
|
73
|
+
*
|
|
74
|
+
* @param webhookId - The webhook.
|
|
75
|
+
* @param changes - The fields to change.
|
|
76
|
+
* @returns The updated webhook.
|
|
77
|
+
* @throws AuthError NOT_FOUND when there is no such webhook.
|
|
78
|
+
* @throws AuthError UNSAFE_WEBHOOK_URL or VALIDATION_ERROR for a bad URL or event name.
|
|
79
|
+
*/
|
|
80
|
+
async updateWebhook(webhookId, changes) {
|
|
81
|
+
const webhooks = this.database.getRepository(entities_1.WebhookEntity);
|
|
82
|
+
const webhook = await webhooks.findOne({ where: { id: webhookId } });
|
|
83
|
+
if (!webhook)
|
|
84
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.notFound);
|
|
85
|
+
if (changes.url !== undefined) {
|
|
86
|
+
await this.assertUrlIsSafe(changes.url);
|
|
87
|
+
webhook.url = changes.url;
|
|
88
|
+
}
|
|
89
|
+
if (changes.eventNames !== undefined)
|
|
90
|
+
webhook.eventNames = this.validateEventNames(changes.eventNames);
|
|
91
|
+
if (changes.description !== undefined)
|
|
92
|
+
webhook.description = changes.description;
|
|
93
|
+
if (changes.isEnabled !== undefined)
|
|
94
|
+
webhook.isEnabled = changes.isEnabled;
|
|
95
|
+
return { success: true, webhook: this.toSummary(await webhooks.save(webhook)) };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Deletes a webhook (its deliveries are deleted with it).
|
|
99
|
+
*
|
|
100
|
+
* @param webhookId - The webhook.
|
|
101
|
+
* @returns `{ success: true }`.
|
|
102
|
+
* @throws AuthError NOT_FOUND when there is no such webhook.
|
|
103
|
+
*/
|
|
104
|
+
async deleteWebhook(webhookId) {
|
|
105
|
+
const result = await this.database.getRepository(entities_1.WebhookEntity).delete({ id: webhookId });
|
|
106
|
+
if (!result.affected)
|
|
107
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.notFound);
|
|
108
|
+
return { success: true };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Replaces the signing secret (e.g. it leaked). The old one stops working at once.
|
|
112
|
+
*
|
|
113
|
+
* @param webhookId - The webhook.
|
|
114
|
+
* @returns The new plain signing secret, shown only this once.
|
|
115
|
+
* @throws AuthError NOT_FOUND when there is no such webhook.
|
|
116
|
+
*/
|
|
117
|
+
async rotateSigningSecret(webhookId) {
|
|
118
|
+
const signingSecret = this.createSigningSecret();
|
|
119
|
+
const result = await this.database.getRepository(entities_1.WebhookEntity).update({ id: webhookId }, { encryptedSigningSecret: await this.encryption.encrypt(signingSecret, this.encryptionContext(webhookId)) });
|
|
120
|
+
if (!result.affected)
|
|
121
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.notFound);
|
|
122
|
+
return { success: true, signingSecret };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Recent deliveries of one webhook, newest first, for debugging a receiver.
|
|
126
|
+
*
|
|
127
|
+
* @param webhookId - The webhook.
|
|
128
|
+
* @param page - 1-based page number.
|
|
129
|
+
* @param pageSize - Rows per page (capped at MAXIMUM_PAGE_SIZE).
|
|
130
|
+
* @returns One page of deliveries, plus the total.
|
|
131
|
+
*/
|
|
132
|
+
async listDeliveries(webhookId, page = 1, pageSize = constants_1.DEFAULT_PAGE_SIZE) {
|
|
133
|
+
const take = Math.min(Math.max(pageSize, 1), constants_1.MAXIMUM_PAGE_SIZE);
|
|
134
|
+
const [deliveries, total] = await this.database.getRepository(entities_1.WebhookDeliveryEntity).findAndCount({
|
|
135
|
+
where: { webhookId },
|
|
136
|
+
order: { createdAt: constants_1.SORT_ORDER.newestFirst },
|
|
137
|
+
skip: (Math.max(page, 1) - 1) * take,
|
|
138
|
+
take,
|
|
139
|
+
});
|
|
140
|
+
return { success: true, deliveries, total, page, pageSize: take };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Sends a failed (or any) delivery again, right away, with a fresh set of retries.
|
|
144
|
+
*
|
|
145
|
+
* @param webhookId - The webhook the delivery belongs to.
|
|
146
|
+
* @param deliveryId - The delivery.
|
|
147
|
+
* @returns `{ success: true }`.
|
|
148
|
+
* @throws AuthError NOT_FOUND when the delivery doesn't exist for this webhook.
|
|
149
|
+
*/
|
|
150
|
+
async retryDelivery(webhookId, deliveryId) {
|
|
151
|
+
const result = await this.database
|
|
152
|
+
.getRepository(entities_1.WebhookDeliveryEntity)
|
|
153
|
+
.update({ id: deliveryId, webhookId }, { status: constants_1.WEBHOOK_DELIVERY_STATUS.pending, nextAttemptAt: new Date(), attemptCount: 0 });
|
|
154
|
+
if (!result.affected)
|
|
155
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.notFound);
|
|
156
|
+
return { success: true };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Queues a "webhook.test" delivery so the receiver can be checked end to end.
|
|
160
|
+
*
|
|
161
|
+
* @param webhookId - The webhook.
|
|
162
|
+
* @returns `{ success: true }`.
|
|
163
|
+
* @throws AuthError NOT_FOUND when there is no such webhook.
|
|
164
|
+
*/
|
|
165
|
+
async sendTestDelivery(webhookId) {
|
|
166
|
+
if (!(await this.database.getRepository(entities_1.WebhookEntity).exists({ where: { id: webhookId } })))
|
|
167
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.notFound);
|
|
168
|
+
await this.database.getRepository(entities_1.WebhookDeliveryEntity).insert((0, typeorm_row_1.typeormRow)({
|
|
169
|
+
webhookId,
|
|
170
|
+
eventName: constants_1.WEBHOOK_TEST_EVENT_NAME,
|
|
171
|
+
payload: { name: constants_1.WEBHOOK_TEST_EVENT_NAME, userId: null, data: { message: constants_1.WEBHOOK_TEST_MESSAGE }, occurredAt: new Date().toISOString() },
|
|
172
|
+
status: constants_1.WEBHOOK_DELIVERY_STATUS.pending,
|
|
173
|
+
nextAttemptAt: new Date(),
|
|
174
|
+
}));
|
|
175
|
+
return { success: true };
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Reads a webhook's signing secret. Used by the delivery worker.
|
|
179
|
+
*
|
|
180
|
+
* @param webhook - The webhook row.
|
|
181
|
+
* @returns The plain signing secret.
|
|
182
|
+
*/
|
|
183
|
+
decryptSigningSecret(webhook) {
|
|
184
|
+
return this.encryption.decrypt(webhook.encryptedSigningSecret, this.encryptionContext(webhook.id));
|
|
185
|
+
}
|
|
186
|
+
// -------------------------------------------------------------------------------------------
|
|
187
|
+
/**
|
|
188
|
+
* @param url - The webhook URL.
|
|
189
|
+
* @throws AuthError UNSAFE_WEBHOOK_URL (with `problem`) when the server must not call it.
|
|
190
|
+
*/
|
|
191
|
+
async assertUrlIsSafe(url) {
|
|
192
|
+
const problem = await (0, network_safety_1.findUrlSafetyProblem)(url);
|
|
193
|
+
if (problem)
|
|
194
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.unsafeWebhookUrl, { problem });
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* @param eventNames - The event names the admin chose.
|
|
198
|
+
* @returns The names without duplicates.
|
|
199
|
+
* @throws AuthError VALIDATION_ERROR listing any names that are not real events.
|
|
200
|
+
*/
|
|
201
|
+
validateEventNames(eventNames) {
|
|
202
|
+
const unknownNames = eventNames.filter((name) => !KNOWN_EVENT_NAMES.has(name));
|
|
203
|
+
if (unknownNames.length > 0) {
|
|
204
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.validationFailed, { problems: [constants_1.VALIDATION_PROBLEM.unknownEventNames(unknownNames)] });
|
|
205
|
+
}
|
|
206
|
+
return [...new Set(eventNames)];
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* @returns A new random signing secret like "whsec_...".
|
|
210
|
+
*/
|
|
211
|
+
createSigningSecret() {
|
|
212
|
+
return `${constants_1.WEBHOOK_SECRET_PREFIX}${(0, node_crypto_1.randomBytes)(constants_1.WEBHOOK_SECRET_BYTES).toString(constants_1.TEXT_ENCODING.base64Url)}`;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Binds the encrypted secret to its webhook, so a secret copied to another row won't decrypt.
|
|
216
|
+
*
|
|
217
|
+
* @param webhookId - The webhook.
|
|
218
|
+
* @returns The context for `EncryptionService`.
|
|
219
|
+
*/
|
|
220
|
+
encryptionContext(webhookId) {
|
|
221
|
+
return { purpose: constants_1.ENCRYPTION_PURPOSE.webhookSigningSecret, webhookId };
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* @param webhook - The webhook row.
|
|
225
|
+
* @returns The webhook without its encrypted secret, safe to show to admins.
|
|
226
|
+
*/
|
|
227
|
+
toSummary(webhook) {
|
|
228
|
+
const { encryptedSigningSecret: _hiddenSecret, ...summary } = webhook;
|
|
229
|
+
return summary;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
exports.WebhooksService = WebhooksService;
|
|
233
|
+
exports.WebhooksService = WebhooksService = __decorate([
|
|
234
|
+
(0, common_1.Injectable)(),
|
|
235
|
+
__param(0, (0, common_1.Inject)(constants_1.AUTH_OPTIONS_TOKEN)),
|
|
236
|
+
__metadata("design:paramtypes", [Object, encryption_service_1.EncryptionService])
|
|
237
|
+
], WebhooksService);
|
|
238
|
+
//# sourceMappingURL=webhooks.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.service.js","sourceRoot":"","sources":["../../../src/features/webhooks/webhooks.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { AccessTokenClaims } from '../features/sessions/session.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Who is calling the route. Exactly one of the two fields is set:
|
|
4
|
+
* - `user`: a person signed in with an access token.
|
|
5
|
+
* - `apiKey`: another server calling with an API key.
|
|
6
|
+
*/
|
|
7
|
+
export interface AuthenticatedCaller {
|
|
8
|
+
user?: AccessTokenClaims;
|
|
9
|
+
apiKey?: {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
scopes: string[];
|
|
13
|
+
organizationId: string | null;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Anyone may call this route or controller, signed in or not.
|
|
18
|
+
*
|
|
19
|
+
* @returns A route or controller decorator.
|
|
20
|
+
*/
|
|
21
|
+
export declare const Public: () => import("@nestjs/common").CustomDecorator<"techelusive-auth:public-route">;
|
|
22
|
+
/**
|
|
23
|
+
* Only users with at least one of these roles may call the route.
|
|
24
|
+
*
|
|
25
|
+
* @param roleNames - The roles that are allowed.
|
|
26
|
+
* @returns A route or controller decorator.
|
|
27
|
+
* @example
|
|
28
|
+
* @Roles('seller', 'admin')
|
|
29
|
+
*/
|
|
30
|
+
export declare const Roles: (...roleNames: string[]) => import("@nestjs/common").CustomDecorator<"techelusive-auth:required-roles">;
|
|
31
|
+
/**
|
|
32
|
+
* Only admins may call the route. Which roles count as "admin" comes from the settings
|
|
33
|
+
* (admin.adminRoles), so it can be changed at runtime. Also checks that the session is still active.
|
|
34
|
+
*
|
|
35
|
+
* @returns A route or controller decorator.
|
|
36
|
+
*/
|
|
37
|
+
export declare const AdminOnly: () => <TFunction extends Function, Y>(target: TFunction | object, propertyKey?: string | symbol, descriptor?: TypedPropertyDescriptor<Y>) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Before running the route, confirm in the database that the session has not been signed out
|
|
40
|
+
* and the account is still ACTIVE. Use it on sensitive routes (payments, changing email, admin work).
|
|
41
|
+
* Without it, a signed-out token keeps working until it expires (a few minutes).
|
|
42
|
+
*
|
|
43
|
+
* @returns A route or controller decorator.
|
|
44
|
+
*/
|
|
45
|
+
export declare const CheckSession: () => import("@nestjs/common").CustomDecorator<"techelusive-auth:check-session">;
|
|
46
|
+
/**
|
|
47
|
+
* Lets servers call the route with an API key that has ALL of these scopes.
|
|
48
|
+
* Signed-in users may still call it as normal.
|
|
49
|
+
*
|
|
50
|
+
* @param scopes - The scopes the key must have.
|
|
51
|
+
* @returns A route or controller decorator.
|
|
52
|
+
* @example
|
|
53
|
+
* @ApiKeyScopes('rides:read')
|
|
54
|
+
*/
|
|
55
|
+
export declare const ApiKeyScopes: (...scopes: string[]) => import("@nestjs/common").CustomDecorator<"techelusive-auth:api-key-scopes">;
|
|
56
|
+
/**
|
|
57
|
+
* Controller parameter: the signed-in user's token claims.
|
|
58
|
+
* @Get('profile') getProfile(@CurrentUser() user: AccessTokenClaims) { ... }
|
|
59
|
+
* @Get('id') getId(@CurrentUser('sub') userId: string) { ... }
|
|
60
|
+
*/
|
|
61
|
+
export declare const CurrentUser: (...dataOrPipes: (keyof AccessTokenClaims | import("@nestjs/common").PipeTransform<any, any> | import("@nestjs/common").Type<import("@nestjs/common").PipeTransform<any, any>> | undefined)[]) => ParameterDecorator;
|
|
62
|
+
/** Controller parameter: the signed-in user's id (the token's "sub" claim). */
|
|
63
|
+
export declare const CurrentUserId: (...dataOrPipes: unknown[]) => ParameterDecorator;
|
|
64
|
+
/** Controller parameter: the full caller (user or API key). Useful on routes that accept both. */
|
|
65
|
+
export declare const CurrentCaller: (...dataOrPipes: unknown[]) => ParameterDecorator;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CurrentCaller = exports.CurrentUserId = exports.CurrentUser = exports.ApiKeyScopes = exports.CheckSession = exports.AdminOnly = exports.Roles = exports.Public = void 0;
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
5
|
+
const constants_1 = require("../constants.js");
|
|
6
|
+
/**
|
|
7
|
+
* Anyone may call this route or controller, signed in or not.
|
|
8
|
+
*
|
|
9
|
+
* @returns A route or controller decorator.
|
|
10
|
+
*/
|
|
11
|
+
const Public = () => (0, common_1.SetMetadata)(constants_1.METADATA_KEY.publicRoute, true);
|
|
12
|
+
exports.Public = Public;
|
|
13
|
+
/**
|
|
14
|
+
* Only users with at least one of these roles may call the route.
|
|
15
|
+
*
|
|
16
|
+
* @param roleNames - The roles that are allowed.
|
|
17
|
+
* @returns A route or controller decorator.
|
|
18
|
+
* @example
|
|
19
|
+
* @Roles('seller', 'admin')
|
|
20
|
+
*/
|
|
21
|
+
const Roles = (...roleNames) => (0, common_1.SetMetadata)(constants_1.METADATA_KEY.requiredRoles, roleNames);
|
|
22
|
+
exports.Roles = Roles;
|
|
23
|
+
/**
|
|
24
|
+
* Only admins may call the route. Which roles count as "admin" comes from the settings
|
|
25
|
+
* (admin.adminRoles), so it can be changed at runtime. Also checks that the session is still active.
|
|
26
|
+
*
|
|
27
|
+
* @returns A route or controller decorator.
|
|
28
|
+
*/
|
|
29
|
+
const AdminOnly = () => (0, common_1.applyDecorators)((0, common_1.SetMetadata)(constants_1.METADATA_KEY.adminOnly, true), (0, exports.CheckSession)());
|
|
30
|
+
exports.AdminOnly = AdminOnly;
|
|
31
|
+
/**
|
|
32
|
+
* Before running the route, confirm in the database that the session has not been signed out
|
|
33
|
+
* and the account is still ACTIVE. Use it on sensitive routes (payments, changing email, admin work).
|
|
34
|
+
* Without it, a signed-out token keeps working until it expires (a few minutes).
|
|
35
|
+
*
|
|
36
|
+
* @returns A route or controller decorator.
|
|
37
|
+
*/
|
|
38
|
+
const CheckSession = () => (0, common_1.SetMetadata)(constants_1.METADATA_KEY.checkSessionInDatabase, true);
|
|
39
|
+
exports.CheckSession = CheckSession;
|
|
40
|
+
/**
|
|
41
|
+
* Lets servers call the route with an API key that has ALL of these scopes.
|
|
42
|
+
* Signed-in users may still call it as normal.
|
|
43
|
+
*
|
|
44
|
+
* @param scopes - The scopes the key must have.
|
|
45
|
+
* @returns A route or controller decorator.
|
|
46
|
+
* @example
|
|
47
|
+
* @ApiKeyScopes('rides:read')
|
|
48
|
+
*/
|
|
49
|
+
const ApiKeyScopes = (...scopes) => (0, common_1.SetMetadata)(constants_1.METADATA_KEY.apiKeyScopes, scopes);
|
|
50
|
+
exports.ApiKeyScopes = ApiKeyScopes;
|
|
51
|
+
/**
|
|
52
|
+
* Controller parameter: the signed-in user's token claims.
|
|
53
|
+
* @Get('profile') getProfile(@CurrentUser() user: AccessTokenClaims) { ... }
|
|
54
|
+
* @Get('id') getId(@CurrentUser('sub') userId: string) { ... }
|
|
55
|
+
*/
|
|
56
|
+
exports.CurrentUser = (0, common_1.createParamDecorator)((claimName, executionContext) => {
|
|
57
|
+
const caller = executionContext.switchToHttp().getRequest()[constants_1.CALLER_REQUEST_PROPERTY];
|
|
58
|
+
const user = caller?.user;
|
|
59
|
+
return claimName ? user?.[claimName] : user;
|
|
60
|
+
});
|
|
61
|
+
/** Controller parameter: the signed-in user's id (the token's "sub" claim). */
|
|
62
|
+
exports.CurrentUserId = (0, common_1.createParamDecorator)((_unused, executionContext) => {
|
|
63
|
+
const caller = executionContext.switchToHttp().getRequest()[constants_1.CALLER_REQUEST_PROPERTY];
|
|
64
|
+
return caller?.user?.sub;
|
|
65
|
+
});
|
|
66
|
+
/** Controller parameter: the full caller (user or API key). Useful on routes that accept both. */
|
|
67
|
+
exports.CurrentCaller = (0, common_1.createParamDecorator)((_unused, executionContext) => executionContext.switchToHttp().getRequest()[constants_1.CALLER_REQUEST_PROPERTY]);
|
|
68
|
+
//# sourceMappingURL=auth.decorators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.decorators.js","sourceRoot":"","sources":["../../src/guards/auth.decorators.ts"],"names":[],"mappings":";;;AAAA,2CAAsG;AACtG,2CAAoE;AAapE;;;;GAIG;AACI,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,IAAA,oBAAW,EAAC,wBAAY,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;AAA3D,QAAA,MAAM,UAAqD;AAExE;;;;;;;GAOG;AACI,MAAM,KAAK,GAAG,CAAC,GAAG,SAAmB,EAAE,EAAE,CAAC,IAAA,oBAAW,EAAC,wBAAY,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;AAAvF,QAAA,KAAK,SAAkF;AAEpG;;;;;GAKG;AACI,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC,IAAA,wBAAe,EAAC,IAAA,oBAAW,EAAC,wBAAY,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,IAAA,oBAAY,GAAE,CAAC,CAAC;AAA7F,QAAA,SAAS,aAAoF;AAE1G;;;;;;GAMG;AACI,MAAM,YAAY,GAAG,GAAG,EAAE,CAAC,IAAA,oBAAW,EAAC,wBAAY,CAAC,sBAAsB,EAAE,IAAI,CAAC,CAAC;AAA5E,QAAA,YAAY,gBAAgE;AAEzF;;;;;;;;GAQG;AACI,MAAM,YAAY,GAAG,CAAC,GAAG,MAAgB,EAAE,EAAE,CAAC,IAAA,oBAAW,EAAC,wBAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;AAAvF,QAAA,YAAY,gBAA2E;AAEpG;;;;GAIG;AACU,QAAA,WAAW,GAAG,IAAA,6BAAoB,EAC7C,CAAC,SAA8C,EAAE,gBAAkC,EAAE,EAAE;IACrF,MAAM,MAAM,GAAoC,gBAAgB,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC,mCAAuB,CAAC,CAAC;IACtH,MAAM,IAAI,GAAG,MAAM,EAAE,IAAI,CAAC;IAC1B,OAAO,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9C,CAAC,CACF,CAAC;AAEF,+EAA+E;AAClE,QAAA,aAAa,GAAG,IAAA,6BAAoB,EAAC,CAAC,OAAgB,EAAE,gBAAkC,EAAsB,EAAE;IAC7H,MAAM,MAAM,GAAoC,gBAAgB,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC,mCAAuB,CAAC,CAAC;IACtH,OAAO,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC;AAC3B,CAAC,CAAC,CAAC;AAEH,kGAAkG;AACrF,QAAA,aAAa,GAAG,IAAA,6BAAoB,EAC/C,CAAC,OAAgB,EAAE,gBAAkC,EAAmC,EAAE,CACxF,gBAAgB,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC,mCAAuB,CAAC,CACxE,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { CanActivate, ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import { Reflector } from '@nestjs/core';
|
|
3
|
+
import { ApiKeysService } from '../features/api-keys/api-keys.service.js';
|
|
4
|
+
import { SessionService } from '../features/sessions/session.service.js';
|
|
5
|
+
import { SettingsService } from '../features/settings/settings.service.js';
|
|
6
|
+
/**
|
|
7
|
+
* Runs before every route (when installed as the app-wide guard) and decides who may call it.
|
|
8
|
+
*
|
|
9
|
+
* Checks, in this order:
|
|
10
|
+
* - @Public() routes let everyone in (a token is still read if sent, so @CurrentUser() works
|
|
11
|
+
* on public routes too).
|
|
12
|
+
* - An "x-api-key" header on a route marked @ApiKeyScopes(): the key and its scopes are checked.
|
|
13
|
+
* - Otherwise an "Authorization: Bearer <access token>" header is required.
|
|
14
|
+
* - @CheckSession() / @AdminOnly(): the database confirms the session is still active.
|
|
15
|
+
* - @Roles() / @AdminOnly(): the user needs one of the roles.
|
|
16
|
+
*/
|
|
17
|
+
export declare class AuthGuard implements CanActivate {
|
|
18
|
+
private readonly reflector;
|
|
19
|
+
private readonly sessions;
|
|
20
|
+
private readonly apiKeys;
|
|
21
|
+
private readonly settings;
|
|
22
|
+
constructor(reflector: Reflector, sessions: SessionService, apiKeys: ApiKeysService, settings: SettingsService);
|
|
23
|
+
/**
|
|
24
|
+
* Decides whether the request may reach the route (see the class comment for the order of checks).
|
|
25
|
+
* On success, the caller is attached to the request for @CurrentUser() and friends.
|
|
26
|
+
*
|
|
27
|
+
* @param executionContext - Nest's view of the current request.
|
|
28
|
+
* @returns True when the request may continue.
|
|
29
|
+
* @throws AuthError UNAUTHORIZED when no access token was sent.
|
|
30
|
+
* @throws AuthError INVALID_TOKEN when the token is bad, or the session has ended (on checked routes).
|
|
31
|
+
* @throws AuthError INVALID_API_KEY or FORBIDDEN for a bad API key or a missing scope.
|
|
32
|
+
* @throws AuthError FORBIDDEN when the user lacks a required role.
|
|
33
|
+
*/
|
|
34
|
+
canActivate(executionContext: ExecutionContext): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* @param authorizationHeader - The Authorization header.
|
|
37
|
+
* @returns The token from "Bearer <token>", or undefined.
|
|
38
|
+
*/
|
|
39
|
+
private readBearerToken;
|
|
40
|
+
/**
|
|
41
|
+
* Stores who is calling on the request, for the @CurrentUser(), @CurrentUserId() and @CurrentCaller() decorators.
|
|
42
|
+
*
|
|
43
|
+
* @param request - The HTTP request.
|
|
44
|
+
* @param caller - The signed-in user or the API key.
|
|
45
|
+
*/
|
|
46
|
+
private attachCaller;
|
|
47
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
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.AuthGuard = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const core_1 = require("@nestjs/core");
|
|
15
|
+
const auth_error_1 = require("../common/auth-error.js");
|
|
16
|
+
const constants_1 = require("../constants.js");
|
|
17
|
+
const api_keys_service_1 = require("../features/api-keys/api-keys.service.js");
|
|
18
|
+
const session_service_1 = require("../features/sessions/session.service.js");
|
|
19
|
+
const settings_service_1 = require("../features/settings/settings.service.js");
|
|
20
|
+
/**
|
|
21
|
+
* Runs before every route (when installed as the app-wide guard) and decides who may call it.
|
|
22
|
+
*
|
|
23
|
+
* Checks, in this order:
|
|
24
|
+
* - @Public() routes let everyone in (a token is still read if sent, so @CurrentUser() works
|
|
25
|
+
* on public routes too).
|
|
26
|
+
* - An "x-api-key" header on a route marked @ApiKeyScopes(): the key and its scopes are checked.
|
|
27
|
+
* - Otherwise an "Authorization: Bearer <access token>" header is required.
|
|
28
|
+
* - @CheckSession() / @AdminOnly(): the database confirms the session is still active.
|
|
29
|
+
* - @Roles() / @AdminOnly(): the user needs one of the roles.
|
|
30
|
+
*/
|
|
31
|
+
let AuthGuard = class AuthGuard {
|
|
32
|
+
reflector;
|
|
33
|
+
sessions;
|
|
34
|
+
apiKeys;
|
|
35
|
+
settings;
|
|
36
|
+
constructor(reflector, sessions, apiKeys, settings) {
|
|
37
|
+
this.reflector = reflector;
|
|
38
|
+
this.sessions = sessions;
|
|
39
|
+
this.apiKeys = apiKeys;
|
|
40
|
+
this.settings = settings;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Decides whether the request may reach the route (see the class comment for the order of checks).
|
|
44
|
+
* On success, the caller is attached to the request for @CurrentUser() and friends.
|
|
45
|
+
*
|
|
46
|
+
* @param executionContext - Nest's view of the current request.
|
|
47
|
+
* @returns True when the request may continue.
|
|
48
|
+
* @throws AuthError UNAUTHORIZED when no access token was sent.
|
|
49
|
+
* @throws AuthError INVALID_TOKEN when the token is bad, or the session has ended (on checked routes).
|
|
50
|
+
* @throws AuthError INVALID_API_KEY or FORBIDDEN for a bad API key or a missing scope.
|
|
51
|
+
* @throws AuthError FORBIDDEN when the user lacks a required role.
|
|
52
|
+
*/
|
|
53
|
+
async canActivate(executionContext) {
|
|
54
|
+
if (executionContext.getType() !== constants_1.HTTP_CONTEXT_TYPE)
|
|
55
|
+
return true;
|
|
56
|
+
const request = executionContext.switchToHttp().getRequest();
|
|
57
|
+
const readMetadata = (key) => this.reflector.getAllAndOverride(key, [executionContext.getHandler(), executionContext.getClass()]);
|
|
58
|
+
const isPublicRoute = readMetadata(constants_1.METADATA_KEY.publicRoute) === true;
|
|
59
|
+
const requiredApiKeyScopes = readMetadata(constants_1.METADATA_KEY.apiKeyScopes);
|
|
60
|
+
const presentedApiKey = request.headers?.[constants_1.API_KEY_HEADER];
|
|
61
|
+
const presentedAccessToken = this.readBearerToken(request.headers?.[constants_1.HTTP_HEADER.authorization]);
|
|
62
|
+
// Public route: a token is optional, and a bad one is simply ignored.
|
|
63
|
+
if (isPublicRoute) {
|
|
64
|
+
if (presentedAccessToken) {
|
|
65
|
+
const claims = await this.sessions.verifyAccessToken(presentedAccessToken).catch(() => undefined);
|
|
66
|
+
if (claims)
|
|
67
|
+
this.attachCaller(request, { user: claims });
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
// Server-to-server call with an API key.
|
|
72
|
+
if (presentedApiKey !== undefined && requiredApiKeyScopes) {
|
|
73
|
+
const apiKey = await this.apiKeys.verifyApiKey(presentedApiKey);
|
|
74
|
+
const hasEveryScope = requiredApiKeyScopes.every((scope) => apiKey.scopes.includes(scope));
|
|
75
|
+
if (!hasEveryScope)
|
|
76
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.missingApiKeyScope);
|
|
77
|
+
this.attachCaller(request, {
|
|
78
|
+
apiKey: { id: apiKey.id, name: apiKey.name, scopes: apiKey.scopes, organizationId: apiKey.organizationId },
|
|
79
|
+
});
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
// Everything else needs a signed-in user.
|
|
83
|
+
if (!presentedAccessToken)
|
|
84
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.unauthorized);
|
|
85
|
+
const claims = await this.sessions.verifyAccessToken(presentedAccessToken);
|
|
86
|
+
// Optional live check that the session and account are still active.
|
|
87
|
+
const isAdminOnlyRoute = readMetadata(constants_1.METADATA_KEY.adminOnly) === true;
|
|
88
|
+
const mustCheckSession = isAdminOnlyRoute || readMetadata(constants_1.METADATA_KEY.checkSessionInDatabase) === true;
|
|
89
|
+
if (mustCheckSession && !(await this.sessions.isSessionStillAllowed(claims.sid, claims.sub))) {
|
|
90
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidAccessToken);
|
|
91
|
+
}
|
|
92
|
+
// Roles.
|
|
93
|
+
const requiredRoles = readMetadata(constants_1.METADATA_KEY.requiredRoles) ?? [];
|
|
94
|
+
if (requiredRoles.length > 0 && !requiredRoles.some((role) => claims.roles.includes(role))) {
|
|
95
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.forbidden);
|
|
96
|
+
}
|
|
97
|
+
if (isAdminOnlyRoute) {
|
|
98
|
+
const { adminRoles } = (await this.settings.getSettings()).admin;
|
|
99
|
+
if (!adminRoles.some((role) => claims.roles.includes(role)))
|
|
100
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.forbidden);
|
|
101
|
+
}
|
|
102
|
+
this.attachCaller(request, { user: claims });
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* @param authorizationHeader - The Authorization header.
|
|
107
|
+
* @returns The token from "Bearer <token>", or undefined.
|
|
108
|
+
*/
|
|
109
|
+
readBearerToken(authorizationHeader) {
|
|
110
|
+
if (typeof authorizationHeader !== 'string')
|
|
111
|
+
return undefined;
|
|
112
|
+
return constants_1.BEARER_TOKEN_PATTERN.exec(authorizationHeader)?.[1];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Stores who is calling on the request, for the @CurrentUser(), @CurrentUserId() and @CurrentCaller() decorators.
|
|
116
|
+
*
|
|
117
|
+
* @param request - The HTTP request.
|
|
118
|
+
* @param caller - The signed-in user or the API key.
|
|
119
|
+
*/
|
|
120
|
+
attachCaller(request, caller) {
|
|
121
|
+
request[constants_1.CALLER_REQUEST_PROPERTY] = caller;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
exports.AuthGuard = AuthGuard;
|
|
125
|
+
exports.AuthGuard = AuthGuard = __decorate([
|
|
126
|
+
(0, common_1.Injectable)(),
|
|
127
|
+
__metadata("design:paramtypes", [core_1.Reflector,
|
|
128
|
+
session_service_1.SessionService,
|
|
129
|
+
api_keys_service_1.ApiKeysService,
|
|
130
|
+
settings_service_1.SettingsService])
|
|
131
|
+
], AuthGuard);
|
|
132
|
+
//# sourceMappingURL=auth.guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.guard.js","sourceRoot":"","sources":["../../src/guards/auth.guard.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @techelusive/nest-auth: reusable authentication for NestJS apps.
|
|
3
|
+
* Everything an app needs is exported from here.
|
|
4
|
+
*/
|
|
5
|
+
export { AuthModule } from './auth.module';
|
|
6
|
+
export type { AuthEmail, AuthEvent, AuthModuleConfig, AuthOptions, EmailSender, EncryptionOptions, PasskeyOptions, SmsSender, } from './auth-options';
|
|
7
|
+
export { AuthGuard } from './guards/auth.guard';
|
|
8
|
+
export { AdminOnly, ApiKeyScopes, CheckSession, CurrentCaller, CurrentUser, CurrentUserId, Public, Roles, } from './guards/auth.decorators';
|
|
9
|
+
export type { AuthenticatedCaller } from './guards/auth.decorators';
|
|
10
|
+
export { CurrentRequest, SYSTEM_REQUEST } from './common/request-context';
|
|
11
|
+
export type { RequestContext } from './common/request-context';
|
|
12
|
+
export * from './database/entities';
|
|
13
|
+
export { AUTH_MIGRATIONS, CreateAuthTables1792600000000 } from './database/create-auth-tables.migration';
|
|
14
|
+
export { EmailChangeAndRetention1792700000000 } from './database/email-change-and-retention.migration';
|
|
15
|
+
export { UsersService } from './features/users/users.service';
|
|
16
|
+
export { DataRetentionService } from './features/maintenance/data-retention.service';
|
|
17
|
+
export type { DataRetentionReport } from './features/maintenance/data-retention.service';
|
|
18
|
+
export { SessionService } from './features/sessions/session.service';
|
|
19
|
+
export { SignInService } from './features/sign-in/sign-in.service';
|
|
20
|
+
export { SettingsService } from './features/settings/settings.service';
|
|
21
|
+
export { AuthEventsService } from './features/events/auth-events.service';
|
|
22
|
+
export { ApiKeysService } from './features/api-keys/api-keys.service';
|
|
23
|
+
export { OrganizationsService } from './features/organizations/organizations.service';
|
|
24
|
+
export { TwoFactorService } from './features/two-factor/two-factor.service';
|
|
25
|
+
export { SigningKeysService } from './features/signing-keys/signing-keys.service';
|
|
26
|
+
export { WebhooksService } from './features/webhooks/webhooks.service';
|
|
27
|
+
export type { AccessTokenClaims, SignInMethod, TokenSet } from './features/sessions/session.types';
|
|
28
|
+
export type { SignInResult, SignedInResult, TwoFactorRequiredResult } from './features/sign-in/sign-in.types';
|
|
29
|
+
export type { PendingVerification, PublicUserProfile } from './features/users/users.types';
|
|
30
|
+
export type { AuthSettings, SettingsChanges } from './features/settings/settings.types';
|
|
31
|
+
export { DEFAULT_SETTINGS } from './features/settings/settings.defaults';
|
|
32
|
+
export { AuthError } from './common/auth-error';
|
|
33
|
+
export { ACCOUNT_STATUS, ACCOUNT_STATUSES, AUTH_ERRORS, AUTH_EVENTS, ORGANIZATION_ROLE, ORGANIZATION_ROLES, PENDING_VERIFICATION, ROUTES, SIGN_IN_METHOD, SIGN_IN_STATUS, SOCIAL_PROVIDER, } from './constants';
|
|
34
|
+
export type { AccountStatus, AuthErrorDefinition, AuthErrorName, AuthEventName, OrganizationRole, SocialProvider } from './constants';
|
|
35
|
+
export { Msg91SmsSender } from './integrations/msg91-sms-sender';
|
|
36
|
+
export type { Msg91Options } from './integrations/msg91-sms-sender';
|
|
37
|
+
export { ConsoleEmailSender } from './integrations/console-email-sender';
|
|
38
|
+
export { verifyWebhookSignature } from './features/webhooks/webhook-signature';
|