@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,227 @@
|
|
|
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.AdminService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const typeorm_1 = require("typeorm");
|
|
18
|
+
const constants_1 = require("../../constants.js");
|
|
19
|
+
const entities_1 = require("../../database/entities/index.js");
|
|
20
|
+
const auth_events_service_1 = require("../events/auth-events.service.js");
|
|
21
|
+
const session_service_1 = require("../sessions/session.service.js");
|
|
22
|
+
const settings_service_1 = require("../settings/settings.service.js");
|
|
23
|
+
const sign_in_methods_service_1 = require("../users/sign-in-methods.service.js");
|
|
24
|
+
const users_service_1 = require("../users/users.service.js");
|
|
25
|
+
/** Events counted by GET /auth/admin/stats, by the name they have in the response. */
|
|
26
|
+
const STATS_EVENT_NAMES = {
|
|
27
|
+
signIns: constants_1.AUTH_EVENTS.userSignedIn,
|
|
28
|
+
failedSignIns: constants_1.AUTH_EVENTS.userSignInFailed,
|
|
29
|
+
newUsers: constants_1.AUTH_EVENTS.userCreated,
|
|
30
|
+
refreshTokenReuses: constants_1.AUTH_EVENTS.refreshTokenReused,
|
|
31
|
+
passwordResetsRequested: constants_1.AUTH_EVENTS.passwordResetRequested,
|
|
32
|
+
};
|
|
33
|
+
/** Read-heavy admin operations. Changes to a single user go through UsersService, so the rules stay in one place. */
|
|
34
|
+
let AdminService = class AdminService {
|
|
35
|
+
settingsService;
|
|
36
|
+
users;
|
|
37
|
+
sessions;
|
|
38
|
+
signInMethods;
|
|
39
|
+
events;
|
|
40
|
+
database;
|
|
41
|
+
constructor(options, settingsService, users, sessions, signInMethods, events) {
|
|
42
|
+
this.settingsService = settingsService;
|
|
43
|
+
this.users = users;
|
|
44
|
+
this.sessions = sessions;
|
|
45
|
+
this.signInMethods = signInMethods;
|
|
46
|
+
this.events = events;
|
|
47
|
+
this.database = options.dataSource;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Paged user list with search and filters. Deleted accounts are excluded unless asked for
|
|
51
|
+
* with `status: "DELETED"`.
|
|
52
|
+
*
|
|
53
|
+
* @param filters - Search text, status, role, page and page size.
|
|
54
|
+
* @returns One page of public profiles, plus the total number of matches.
|
|
55
|
+
*/
|
|
56
|
+
async searchUsers(filters) {
|
|
57
|
+
const settings = await this.settingsService.getSettings();
|
|
58
|
+
const { page, pageSize, skip } = this.paging(filters.page, filters.pageSize);
|
|
59
|
+
const query = this.database.getRepository(entities_1.UserEntity).createQueryBuilder('user');
|
|
60
|
+
if (filters.status)
|
|
61
|
+
query.andWhere('user.status = :status', { status: filters.status });
|
|
62
|
+
else
|
|
63
|
+
query.andWhere('user.status <> :deletedStatus', { deletedStatus: constants_1.ACCOUNT_STATUS.deleted });
|
|
64
|
+
if (filters.role)
|
|
65
|
+
query.andWhere(':role = ANY(user.roles)', { role: filters.role });
|
|
66
|
+
if (filters.search?.trim()) {
|
|
67
|
+
// Escape % and _ so the search text is matched literally.
|
|
68
|
+
const escapedSearch = filters.search.trim().replace(constants_1.SQL_LIKE_SPECIAL_CHARACTERS_PATTERN, (character) => constants_1.SQL_LIKE_ESCAPE_CHARACTER + character);
|
|
69
|
+
const pattern = constants_1.SQL_LIKE_ANY_TEXT + escapedSearch + constants_1.SQL_LIKE_ANY_TEXT;
|
|
70
|
+
query.andWhere('(user.email ILIKE :pattern OR user.phone_number ILIKE :pattern OR user.first_name ILIKE :pattern OR user.last_name ILIKE :pattern)', { pattern });
|
|
71
|
+
}
|
|
72
|
+
const [users, total] = await query.orderBy('user.created_at', constants_1.SORT_ORDER.newestFirst).skip(skip).take(pageSize).getManyAndCount();
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
users: users.map((user) => this.users.toPublicProfile(user, settings)),
|
|
76
|
+
total,
|
|
77
|
+
page,
|
|
78
|
+
pageSize,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Everything support staff need about one user: profile, suspension reason, sign-in methods, devices.
|
|
83
|
+
*
|
|
84
|
+
* @param userId - The user.
|
|
85
|
+
* @returns The profile and the extra details.
|
|
86
|
+
* @throws AuthError NOT_FOUND when the user doesn't exist.
|
|
87
|
+
*/
|
|
88
|
+
async getUserDetails(userId) {
|
|
89
|
+
const settings = await this.settingsService.getSettings();
|
|
90
|
+
const user = await this.users.getUserOrThrow(userId);
|
|
91
|
+
const [signInMethods, sessions] = await Promise.all([
|
|
92
|
+
this.signInMethods.listSignInMethods(user, settings),
|
|
93
|
+
this.sessions.listActiveSessions(userId),
|
|
94
|
+
]);
|
|
95
|
+
return {
|
|
96
|
+
success: true,
|
|
97
|
+
user: this.users.toPublicProfile(user, settings),
|
|
98
|
+
suspensionReason: user.suspensionReason,
|
|
99
|
+
statusChangedAt: user.statusChangedAt?.toISOString() ?? null,
|
|
100
|
+
termsAcceptedAt: user.termsAcceptedAt?.toISOString() ?? null,
|
|
101
|
+
privacyPolicyAcceptedAt: user.privacyPolicyAcceptedAt?.toISOString() ?? null,
|
|
102
|
+
signInMethods,
|
|
103
|
+
sessions: sessions.map((session) => ({
|
|
104
|
+
id: session.id,
|
|
105
|
+
signInMethod: session.signInMethod,
|
|
106
|
+
ipAddress: session.ipAddress,
|
|
107
|
+
userAgent: session.userAgent,
|
|
108
|
+
createdAt: session.createdAt.toISOString(),
|
|
109
|
+
lastUsedAt: session.lastUsedAt.toISOString(),
|
|
110
|
+
})),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Paged security history, newest first.
|
|
115
|
+
*
|
|
116
|
+
* @param filters - Optional user id and event name, plus page and page size.
|
|
117
|
+
* @returns One page of audit log entries, plus the total number of matches.
|
|
118
|
+
*/
|
|
119
|
+
async searchAuditLog(filters) {
|
|
120
|
+
const { page, pageSize, skip } = this.paging(filters.page, filters.pageSize);
|
|
121
|
+
const [entries, total] = await this.database.getRepository(entities_1.AuditLogEntity).findAndCount({
|
|
122
|
+
where: {
|
|
123
|
+
...(filters.userId ? { userId: filters.userId } : {}),
|
|
124
|
+
...(filters.eventName ? { eventName: filters.eventName } : {}),
|
|
125
|
+
},
|
|
126
|
+
order: { createdAt: constants_1.SORT_ORDER.newestFirst },
|
|
127
|
+
skip,
|
|
128
|
+
take: pageSize,
|
|
129
|
+
});
|
|
130
|
+
return { success: true, entries, total, page, pageSize };
|
|
131
|
+
}
|
|
132
|
+
// -------------------------------------------------------------------------------------------
|
|
133
|
+
// Runtime settings
|
|
134
|
+
// -------------------------------------------------------------------------------------------
|
|
135
|
+
/**
|
|
136
|
+
* @returns The settings in force: the code settings with any admin changes on top.
|
|
137
|
+
*/
|
|
138
|
+
async getSettings() {
|
|
139
|
+
return { success: true, settings: await this.settingsService.getSettings() };
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Saves admin changes on top of the code settings. Takes effect on every server within seconds.
|
|
143
|
+
*
|
|
144
|
+
* @param changes - Only the sections and fields to change.
|
|
145
|
+
* @param adminUserId - The admin doing it (for the audit log).
|
|
146
|
+
* @param request - Who is asking.
|
|
147
|
+
* @returns The settings now in force.
|
|
148
|
+
* @throws AuthError INVALID_SETTINGS when a value is not allowed.
|
|
149
|
+
*/
|
|
150
|
+
async changeSettings(changes, adminUserId, request) {
|
|
151
|
+
const settings = await this.settingsService.saveAdminChanges(changes, constants_1.PERFORMED_BY.admin(adminUserId));
|
|
152
|
+
await this.events.record({
|
|
153
|
+
name: constants_1.AUTH_EVENTS.settingsChanged,
|
|
154
|
+
userId: adminUserId,
|
|
155
|
+
performedBy: constants_1.PERFORMED_BY.admin(adminUserId),
|
|
156
|
+
request,
|
|
157
|
+
details: { changedSections: Object.keys(changes) },
|
|
158
|
+
});
|
|
159
|
+
return { success: true, settings };
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Throws away all admin changes, back to the settings in code.
|
|
163
|
+
*
|
|
164
|
+
* @param adminUserId - The admin doing it (for the audit log).
|
|
165
|
+
* @param request - Who is asking.
|
|
166
|
+
* @returns The settings now in force.
|
|
167
|
+
*/
|
|
168
|
+
async resetSettings(adminUserId, request) {
|
|
169
|
+
const settings = await this.settingsService.discardAdminChanges();
|
|
170
|
+
await this.events.record({
|
|
171
|
+
name: constants_1.AUTH_EVENTS.settingsChanged,
|
|
172
|
+
userId: adminUserId,
|
|
173
|
+
performedBy: constants_1.PERFORMED_BY.admin(adminUserId),
|
|
174
|
+
request,
|
|
175
|
+
details: { reset: true },
|
|
176
|
+
});
|
|
177
|
+
return { success: true, settings };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Numbers for an admin dashboard or monitoring: accounts by status, signed-in devices, and what
|
|
181
|
+
* happened in the last 24 hours (from the audit log, so it covers every server).
|
|
182
|
+
*
|
|
183
|
+
* @returns The counts and when they were generated.
|
|
184
|
+
*/
|
|
185
|
+
async getStats() {
|
|
186
|
+
const since = new Date(Date.now() - constants_1.ADMIN_STATS_WINDOW_SECONDS * constants_1.SECOND_IN_MILLISECONDS);
|
|
187
|
+
const statuses = Object.values(constants_1.ACCOUNT_STATUS);
|
|
188
|
+
const eventLabels = Object.entries(STATS_EVENT_NAMES);
|
|
189
|
+
const [userCounts, activeSessions, eventCounts, pendingWebhookDeliveries] = await Promise.all([
|
|
190
|
+
Promise.all(statuses.map((status) => this.database.getRepository(entities_1.UserEntity).count({ where: { status } }))),
|
|
191
|
+
this.database.getRepository(entities_1.SessionEntity).count({ where: { revokedAt: (0, typeorm_1.IsNull)(), expiresAt: (0, typeorm_1.MoreThan)(new Date()) } }),
|
|
192
|
+
Promise.all(eventLabels.map(([, eventName]) => this.database.getRepository(entities_1.AuditLogEntity).count({ where: { eventName, createdAt: (0, typeorm_1.MoreThanOrEqual)(since) } }))),
|
|
193
|
+
this.database.getRepository(entities_1.WebhookDeliveryEntity).count({ where: { status: constants_1.WEBHOOK_DELIVERY_STATUS.pending } }),
|
|
194
|
+
]);
|
|
195
|
+
return {
|
|
196
|
+
success: true,
|
|
197
|
+
usersByStatus: Object.fromEntries(statuses.map((status, index) => [status, userCounts[index]])),
|
|
198
|
+
activeSessions,
|
|
199
|
+
last24Hours: Object.fromEntries(eventLabels.map(([label], index) => [label, eventCounts[index]])),
|
|
200
|
+
pendingWebhookDeliveries,
|
|
201
|
+
generatedAt: new Date().toISOString(),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Turns the requested page and page size into safe values (at least 1, at most MAXIMUM_PAGE_SIZE).
|
|
206
|
+
*
|
|
207
|
+
* @param requestedPage - 1-based page number.
|
|
208
|
+
* @param requestedPageSize - Rows per page.
|
|
209
|
+
* @returns The clean page, page size, and how many rows to skip.
|
|
210
|
+
*/
|
|
211
|
+
paging(requestedPage = constants_1.FIRST_PAGE, requestedPageSize = constants_1.DEFAULT_PAGE_SIZE) {
|
|
212
|
+
const page = Math.max(Math.floor(requestedPage) || constants_1.FIRST_PAGE, constants_1.FIRST_PAGE);
|
|
213
|
+
const pageSize = Math.min(Math.max(Math.floor(requestedPageSize) || constants_1.DEFAULT_PAGE_SIZE, 1), constants_1.MAXIMUM_PAGE_SIZE);
|
|
214
|
+
return { page, pageSize, skip: (page - 1) * pageSize };
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
exports.AdminService = AdminService;
|
|
218
|
+
exports.AdminService = AdminService = __decorate([
|
|
219
|
+
(0, common_1.Injectable)(),
|
|
220
|
+
__param(0, (0, common_1.Inject)(constants_1.AUTH_OPTIONS_TOKEN)),
|
|
221
|
+
__metadata("design:paramtypes", [Object, settings_service_1.SettingsService,
|
|
222
|
+
users_service_1.UsersService,
|
|
223
|
+
session_service_1.SessionService,
|
|
224
|
+
sign_in_methods_service_1.SignInMethodsService,
|
|
225
|
+
auth_events_service_1.AuthEventsService])
|
|
226
|
+
], AdminService);
|
|
227
|
+
//# sourceMappingURL=admin.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.service.js","sourceRoot":"","sources":["../../../src/features/admin/admin.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { AuthOptions } from '../../auth-options.js';
|
|
2
|
+
import { RequestContext } from '../../common/request-context.js';
|
|
3
|
+
import { ApiKeyEntity } from '../../database/entities/index.js';
|
|
4
|
+
import { AuthEventsService } from '../events/auth-events.service.js';
|
|
5
|
+
export interface CreateApiKeyInput {
|
|
6
|
+
name: string;
|
|
7
|
+
scopes: string[];
|
|
8
|
+
organizationId?: string | null;
|
|
9
|
+
expiresAt?: Date | null;
|
|
10
|
+
}
|
|
11
|
+
/** An API key as shown in lists. The secret itself is never shown again after creation. */
|
|
12
|
+
export type ApiKeySummary = Omit<ApiKeyEntity, 'keyHash'>;
|
|
13
|
+
/**
|
|
14
|
+
* Keys that let other servers call your API (machine to machine).
|
|
15
|
+
* The full key is returned once, when created. Only its SHA-256 hash is stored.
|
|
16
|
+
*/
|
|
17
|
+
export declare class ApiKeysService {
|
|
18
|
+
private readonly events;
|
|
19
|
+
private readonly logger;
|
|
20
|
+
private readonly database;
|
|
21
|
+
constructor(options: AuthOptions, events: AuthEventsService);
|
|
22
|
+
/**
|
|
23
|
+
* Creates a key. Returns the plain key: show it to the admin once, it cannot be read again.
|
|
24
|
+
*
|
|
25
|
+
* @param input - Name, scopes, and optional organization and expiry date.
|
|
26
|
+
* @param createdByUserId - The admin creating it (for the audit log).
|
|
27
|
+
* @param request - Who is asking.
|
|
28
|
+
* @returns `apiKey` (the plain key) and `details` (what lists show).
|
|
29
|
+
*/
|
|
30
|
+
createApiKey(input: CreateApiKeyInput, createdByUserId: string, request: RequestContext): Promise<{
|
|
31
|
+
apiKey: string;
|
|
32
|
+
details: ApiKeySummary;
|
|
33
|
+
}>;
|
|
34
|
+
/**
|
|
35
|
+
* @param organizationId - When given, only that organization's keys.
|
|
36
|
+
* @returns The keys, newest first, without their hashes.
|
|
37
|
+
*/
|
|
38
|
+
listApiKeys(organizationId?: string): Promise<ApiKeySummary[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Stops a key from working at once.
|
|
41
|
+
*
|
|
42
|
+
* @param apiKeyId - The key.
|
|
43
|
+
* @param revokedByUserId - The admin revoking it (for the audit log).
|
|
44
|
+
* @param request - Who is asking.
|
|
45
|
+
* @throws AuthError NOT_FOUND when there is no such key, or it was already revoked.
|
|
46
|
+
*/
|
|
47
|
+
revokeApiKey(apiKeyId: string, revokedByUserId: string, request: RequestContext): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Used by the guard. Checks a key sent by another server.
|
|
50
|
+
*
|
|
51
|
+
* @param presentedKey - The key from the request header.
|
|
52
|
+
* @returns The key's row (with its scopes) when it is valid, not revoked and not expired.
|
|
53
|
+
* @throws AuthError INVALID_API_KEY otherwise (the same error for every reason, so callers learn nothing).
|
|
54
|
+
*/
|
|
55
|
+
verifyApiKey(presentedKey: unknown): Promise<ApiKeyEntity>;
|
|
56
|
+
/**
|
|
57
|
+
* Records when a key was last used, at most once a minute per key, without slowing the request.
|
|
58
|
+
*
|
|
59
|
+
* @param apiKey - The key that was just used.
|
|
60
|
+
*/
|
|
61
|
+
private updateLastUsedInBackground;
|
|
62
|
+
/**
|
|
63
|
+
* @param apiKey - The key row.
|
|
64
|
+
* @returns The key without its hash, safe to show in lists.
|
|
65
|
+
*/
|
|
66
|
+
private toSummary;
|
|
67
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
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.ApiKeysService = 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
|
+
const auth_events_service_1 = require("../events/auth-events.service.js");
|
|
23
|
+
/**
|
|
24
|
+
* Keys that let other servers call your API (machine to machine).
|
|
25
|
+
* The full key is returned once, when created. Only its SHA-256 hash is stored.
|
|
26
|
+
*/
|
|
27
|
+
let ApiKeysService = class ApiKeysService {
|
|
28
|
+
events;
|
|
29
|
+
logger = new common_1.Logger(constants_1.LOG_CONTEXT.apiKeys);
|
|
30
|
+
database;
|
|
31
|
+
constructor(options, events) {
|
|
32
|
+
this.events = events;
|
|
33
|
+
this.database = options.dataSource;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Creates a key. Returns the plain key: show it to the admin once, it cannot be read again.
|
|
37
|
+
*
|
|
38
|
+
* @param input - Name, scopes, and optional organization and expiry date.
|
|
39
|
+
* @param createdByUserId - The admin creating it (for the audit log).
|
|
40
|
+
* @param request - Who is asking.
|
|
41
|
+
* @returns `apiKey` (the plain key) and `details` (what lists show).
|
|
42
|
+
*/
|
|
43
|
+
async createApiKey(input, createdByUserId, request) {
|
|
44
|
+
const { plainToken: plainKey, tokenHash: keyHash } = (0, random_secrets_1.createRandomToken)(constants_1.TOKEN_PREFIX.apiKey);
|
|
45
|
+
const savedKey = await this.database.getRepository(entities_1.ApiKeyEntity).save({
|
|
46
|
+
name: input.name,
|
|
47
|
+
scopes: [...new Set(input.scopes)],
|
|
48
|
+
organizationId: input.organizationId ?? null,
|
|
49
|
+
expiresAt: input.expiresAt ?? null,
|
|
50
|
+
createdByUserId,
|
|
51
|
+
keyHash,
|
|
52
|
+
keyPreview: plainKey.slice(0, constants_1.TOKEN_PREFIX.apiKey.length + 1 + constants_1.API_KEY_PREVIEW_LENGTH),
|
|
53
|
+
});
|
|
54
|
+
await this.events.record({
|
|
55
|
+
name: constants_1.AUTH_EVENTS.apiKeyCreated,
|
|
56
|
+
userId: createdByUserId,
|
|
57
|
+
performedBy: constants_1.PERFORMED_BY.admin(createdByUserId),
|
|
58
|
+
request,
|
|
59
|
+
details: { apiKeyId: savedKey.id, name: savedKey.name, scopes: savedKey.scopes },
|
|
60
|
+
});
|
|
61
|
+
return { apiKey: plainKey, details: this.toSummary(savedKey) };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* @param organizationId - When given, only that organization's keys.
|
|
65
|
+
* @returns The keys, newest first, without their hashes.
|
|
66
|
+
*/
|
|
67
|
+
async listApiKeys(organizationId) {
|
|
68
|
+
const keys = await this.database.getRepository(entities_1.ApiKeyEntity).find({
|
|
69
|
+
where: organizationId ? { organizationId } : {},
|
|
70
|
+
order: { createdAt: constants_1.SORT_ORDER.newestFirst },
|
|
71
|
+
});
|
|
72
|
+
return keys.map((key) => this.toSummary(key));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Stops a key from working at once.
|
|
76
|
+
*
|
|
77
|
+
* @param apiKeyId - The key.
|
|
78
|
+
* @param revokedByUserId - The admin revoking it (for the audit log).
|
|
79
|
+
* @param request - Who is asking.
|
|
80
|
+
* @throws AuthError NOT_FOUND when there is no such key, or it was already revoked.
|
|
81
|
+
*/
|
|
82
|
+
async revokeApiKey(apiKeyId, revokedByUserId, request) {
|
|
83
|
+
const result = await this.database
|
|
84
|
+
.getRepository(entities_1.ApiKeyEntity)
|
|
85
|
+
.update({ id: apiKeyId, revokedAt: (0, typeorm_1.IsNull)() }, { revokedAt: new Date() });
|
|
86
|
+
if (!result.affected)
|
|
87
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.notFound);
|
|
88
|
+
await this.events.record({
|
|
89
|
+
name: constants_1.AUTH_EVENTS.apiKeyRevoked,
|
|
90
|
+
userId: revokedByUserId,
|
|
91
|
+
performedBy: constants_1.PERFORMED_BY.admin(revokedByUserId),
|
|
92
|
+
request,
|
|
93
|
+
details: { apiKeyId },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Used by the guard. Checks a key sent by another server.
|
|
98
|
+
*
|
|
99
|
+
* @param presentedKey - The key from the request header.
|
|
100
|
+
* @returns The key's row (with its scopes) when it is valid, not revoked and not expired.
|
|
101
|
+
* @throws AuthError INVALID_API_KEY otherwise (the same error for every reason, so callers learn nothing).
|
|
102
|
+
*/
|
|
103
|
+
async verifyApiKey(presentedKey) {
|
|
104
|
+
if (!(0, random_secrets_1.hasRandomTokenFormat)(presentedKey, constants_1.TOKEN_PREFIX.apiKey))
|
|
105
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidApiKey);
|
|
106
|
+
const apiKey = await this.database.getRepository(entities_1.ApiKeyEntity).findOne({
|
|
107
|
+
where: { keyHash: (0, random_secrets_1.sha256Hex)(presentedKey), revokedAt: (0, typeorm_1.IsNull)() },
|
|
108
|
+
});
|
|
109
|
+
const isExpired = apiKey?.expiresAt != null && apiKey.expiresAt.getTime() <= Date.now();
|
|
110
|
+
if (!apiKey || isExpired)
|
|
111
|
+
throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidApiKey);
|
|
112
|
+
this.updateLastUsedInBackground(apiKey);
|
|
113
|
+
return apiKey;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Records when a key was last used, at most once a minute per key, without slowing the request.
|
|
117
|
+
*
|
|
118
|
+
* @param apiKey - The key that was just used.
|
|
119
|
+
*/
|
|
120
|
+
updateLastUsedInBackground(apiKey) {
|
|
121
|
+
const lastUsedMs = apiKey.lastUsedAt?.getTime() ?? 0;
|
|
122
|
+
if (Date.now() - lastUsedMs < constants_1.API_KEY_LAST_USED_UPDATE_SECONDS * constants_1.SECOND_IN_MILLISECONDS)
|
|
123
|
+
return;
|
|
124
|
+
this.database
|
|
125
|
+
.getRepository(entities_1.ApiKeyEntity)
|
|
126
|
+
.update({ id: apiKey.id }, { lastUsedAt: new Date() })
|
|
127
|
+
.catch((error) => this.logger.warn(`Could not update lastUsedAt for API key ${apiKey.id}: ${error.message}`));
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* @param apiKey - The key row.
|
|
131
|
+
* @returns The key without its hash, safe to show in lists.
|
|
132
|
+
*/
|
|
133
|
+
toSummary(apiKey) {
|
|
134
|
+
const { keyHash: _hiddenHash, ...summary } = apiKey;
|
|
135
|
+
return summary;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
exports.ApiKeysService = ApiKeysService;
|
|
139
|
+
exports.ApiKeysService = ApiKeysService = __decorate([
|
|
140
|
+
(0, common_1.Injectable)(),
|
|
141
|
+
__param(0, (0, common_1.Inject)(constants_1.AUTH_OPTIONS_TOKEN)),
|
|
142
|
+
__metadata("design:paramtypes", [Object, auth_events_service_1.AuthEventsService])
|
|
143
|
+
], ApiKeysService);
|
|
144
|
+
//# sourceMappingURL=api-keys.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-keys.service.js","sourceRoot":"","sources":["../../../src/features/api-keys/api-keys.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { RequestContext } from '../../common/request-context.js';
|
|
2
|
+
import { AccessTokenClaims } from '../sessions/session.types.js';
|
|
3
|
+
import { ConfirmEmailChangeInput, RequestEmailChangeInput } from './email-change.dto';
|
|
4
|
+
import { EmailChangeService } from './email-change.service';
|
|
5
|
+
/**
|
|
6
|
+
* Changing the account's email:
|
|
7
|
+
*
|
|
8
|
+
* POST /auth/email/change (signed in) send a confirmation link to the new address
|
|
9
|
+
* POST /auth/email/change/confirm (the link) switch to the new address
|
|
10
|
+
*/
|
|
11
|
+
export declare class EmailChangeController {
|
|
12
|
+
private readonly emailChange;
|
|
13
|
+
constructor(emailChange: EmailChangeService);
|
|
14
|
+
/**
|
|
15
|
+
* POST /auth/email/change — Sends a confirmation link to the new address.
|
|
16
|
+
*
|
|
17
|
+
* @param user - The access token's claims (user id and session id).
|
|
18
|
+
* @param body - `{ newEmail, currentPassword? }`.
|
|
19
|
+
* @param request - Who is asking.
|
|
20
|
+
* @returns A generic success message.
|
|
21
|
+
*/
|
|
22
|
+
requestEmailChange(user: AccessTokenClaims, body: RequestEmailChangeInput, request: RequestContext): Promise<{
|
|
23
|
+
success: true;
|
|
24
|
+
message: "If this email can be used, a confirmation link is on its way to it.";
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* POST /auth/email/change/confirm — Switches to the new address using the token from the link. Public.
|
|
28
|
+
*
|
|
29
|
+
* @param body - `{ token }`.
|
|
30
|
+
* @param request - Who is asking.
|
|
31
|
+
* @returns A success message and the updated profile.
|
|
32
|
+
*/
|
|
33
|
+
confirmEmailChange(body: ConfirmEmailChangeInput, request: RequestContext): Promise<{
|
|
34
|
+
success: true;
|
|
35
|
+
message: "Your email has been changed.";
|
|
36
|
+
user: import("../..").PublicUserProfile;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
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.EmailChangeController = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const refresh_token_cookie_1 = require("../../common/refresh-token-cookie.js");
|
|
18
|
+
const request_context_1 = require("../../common/request-context.js");
|
|
19
|
+
const strict_validation_pipe_1 = require("../../common/strict-validation.pipe.js");
|
|
20
|
+
const constants_1 = require("../../constants.js");
|
|
21
|
+
const auth_decorators_1 = require("../../guards/auth.decorators.js");
|
|
22
|
+
const email_change_dto_1 = require("./email-change.dto");
|
|
23
|
+
const email_change_service_1 = require("./email-change.service");
|
|
24
|
+
/**
|
|
25
|
+
* Changing the account's email:
|
|
26
|
+
*
|
|
27
|
+
* POST /auth/email/change (signed in) send a confirmation link to the new address
|
|
28
|
+
* POST /auth/email/change/confirm (the link) switch to the new address
|
|
29
|
+
*/
|
|
30
|
+
let EmailChangeController = class EmailChangeController {
|
|
31
|
+
emailChange;
|
|
32
|
+
constructor(emailChange) {
|
|
33
|
+
this.emailChange = emailChange;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* POST /auth/email/change — Sends a confirmation link to the new address.
|
|
37
|
+
*
|
|
38
|
+
* @param user - The access token's claims (user id and session id).
|
|
39
|
+
* @param body - `{ newEmail, currentPassword? }`.
|
|
40
|
+
* @param request - Who is asking.
|
|
41
|
+
* @returns A generic success message.
|
|
42
|
+
*/
|
|
43
|
+
requestEmailChange(user, body, request) {
|
|
44
|
+
return this.emailChange.requestEmailChange(user.sub, user.sid, body.newEmail, body.currentPassword, request);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* POST /auth/email/change/confirm — Switches to the new address using the token from the link. Public.
|
|
48
|
+
*
|
|
49
|
+
* @param body - `{ token }`.
|
|
50
|
+
* @param request - Who is asking.
|
|
51
|
+
* @returns A success message and the updated profile.
|
|
52
|
+
*/
|
|
53
|
+
confirmEmailChange(body, request) {
|
|
54
|
+
return this.emailChange.confirmEmailChange(body.token, request);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
exports.EmailChangeController = EmailChangeController;
|
|
58
|
+
__decorate([
|
|
59
|
+
(0, common_1.Post)(constants_1.ROUTES.auth.emailChange),
|
|
60
|
+
(0, auth_decorators_1.CheckSession)(),
|
|
61
|
+
(0, common_1.HttpCode)(common_1.HttpStatus.OK),
|
|
62
|
+
__param(0, (0, auth_decorators_1.CurrentUser)()),
|
|
63
|
+
__param(1, (0, common_1.Body)()),
|
|
64
|
+
__param(2, (0, request_context_1.CurrentRequest)()),
|
|
65
|
+
__metadata("design:type", Function),
|
|
66
|
+
__metadata("design:paramtypes", [Object, email_change_dto_1.RequestEmailChangeInput, Object]),
|
|
67
|
+
__metadata("design:returntype", void 0)
|
|
68
|
+
], EmailChangeController.prototype, "requestEmailChange", null);
|
|
69
|
+
__decorate([
|
|
70
|
+
(0, common_1.Post)(constants_1.ROUTES.auth.emailChangeConfirm),
|
|
71
|
+
(0, auth_decorators_1.Public)(),
|
|
72
|
+
(0, common_1.HttpCode)(common_1.HttpStatus.OK),
|
|
73
|
+
__param(0, (0, common_1.Body)()),
|
|
74
|
+
__param(1, (0, request_context_1.CurrentRequest)()),
|
|
75
|
+
__metadata("design:type", Function),
|
|
76
|
+
__metadata("design:paramtypes", [email_change_dto_1.ConfirmEmailChangeInput, Object]),
|
|
77
|
+
__metadata("design:returntype", void 0)
|
|
78
|
+
], EmailChangeController.prototype, "confirmEmailChange", null);
|
|
79
|
+
exports.EmailChangeController = EmailChangeController = __decorate([
|
|
80
|
+
(0, common_1.Controller)(constants_1.ROUTES.auth.prefix),
|
|
81
|
+
(0, refresh_token_cookie_1.LibraryController)(),
|
|
82
|
+
(0, common_1.UsePipes)(strict_validation_pipe_1.strictValidationPipe),
|
|
83
|
+
__metadata("design:paramtypes", [email_change_service_1.EmailChangeService])
|
|
84
|
+
], EmailChangeController);
|
|
85
|
+
//# sourceMappingURL=email-change.controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-change.controller.js","sourceRoot":"","sources":["../../../src/features/email-change/email-change.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAwF;AACxF,wEAAkE;AAClE,8DAA0E;AAC1E,4EAAuE;AACvE,2CAAqC;AACrC,8DAA6E;AAE7E,yDAAsF;AACtF,iEAA4D;AAE5D;;;;;GAKG;AAII,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IACH;IAA7B,YAA6B,WAA+B;QAA/B,gBAAW,GAAX,WAAW,CAAoB;IAAG,CAAC;IAEhE;;;;;;;OAOG;IAIH,kBAAkB,CAAgB,IAAuB,EAAU,IAA6B,EAAoB,OAAuB;QACzI,OAAO,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;IAC/G,CAAC;IAED;;;;;;OAMG;IAIH,kBAAkB,CAAS,IAA6B,EAAoB,OAAuB;QACjG,OAAO,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAClE,CAAC;CACF,CAAA;AA/BY,sDAAqB;AAchC;IAHC,IAAA,aAAI,EAAC,kBAAM,CAAC,IAAI,CAAC,WAAW,CAAC;IAC7B,IAAA,8BAAY,GAAE;IACd,IAAA,iBAAQ,EAAC,mBAAU,CAAC,EAAE,CAAC;IACJ,WAAA,IAAA,6BAAW,GAAE,CAAA;IAA2B,WAAA,IAAA,aAAI,GAAE,CAAA;IAAiC,WAAA,IAAA,gCAAc,GAAE,CAAA;;6CAA1C,0CAAuB;;+DAE/F;AAYD;IAHC,IAAA,aAAI,EAAC,kBAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC;IACpC,IAAA,wBAAM,GAAE;IACR,IAAA,iBAAQ,EAAC,mBAAU,CAAC,EAAE,CAAC;IACJ,WAAA,IAAA,aAAI,GAAE,CAAA;IAAiC,WAAA,IAAA,gCAAc,GAAE,CAAA;;qCAA1C,0CAAuB;;+DAEvD;gCA9BU,qBAAqB;IAHjC,IAAA,mBAAU,EAAC,kBAAM,CAAC,IAAI,CAAC,MAAM,CAAC;IAC9B,IAAA,wCAAiB,GAAE;IACnB,IAAA,iBAAQ,EAAC,6CAAoB,CAAC;qCAEa,yCAAkB;GADjD,qBAAqB,CA+BjC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** POST /auth/email/change */
|
|
2
|
+
export declare class RequestEmailChangeInput {
|
|
3
|
+
newEmail: string;
|
|
4
|
+
/** Required when the account has a password. Accounts without one must have signed in recently. */
|
|
5
|
+
currentPassword?: string;
|
|
6
|
+
}
|
|
7
|
+
/** POST /auth/email/change/confirm */
|
|
8
|
+
export declare class ConfirmEmailChangeInput {
|
|
9
|
+
token: string;
|
|
10
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
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.ConfirmEmailChangeInput = exports.RequestEmailChangeInput = void 0;
|
|
13
|
+
const class_validator_1 = require("class-validator");
|
|
14
|
+
const constants_1 = require("../../constants.js");
|
|
15
|
+
/** POST /auth/email/change */
|
|
16
|
+
class RequestEmailChangeInput {
|
|
17
|
+
newEmail;
|
|
18
|
+
/** Required when the account has a password. Accounts without one must have signed in recently. */
|
|
19
|
+
currentPassword;
|
|
20
|
+
}
|
|
21
|
+
exports.RequestEmailChangeInput = RequestEmailChangeInput;
|
|
22
|
+
__decorate([
|
|
23
|
+
(0, class_validator_1.IsString)(),
|
|
24
|
+
(0, class_validator_1.MaxLength)(constants_1.EMAIL_MAX_LENGTH),
|
|
25
|
+
__metadata("design:type", String)
|
|
26
|
+
], RequestEmailChangeInput.prototype, "newEmail", void 0);
|
|
27
|
+
__decorate([
|
|
28
|
+
(0, class_validator_1.IsOptional)(),
|
|
29
|
+
(0, class_validator_1.IsString)(),
|
|
30
|
+
(0, class_validator_1.MaxLength)(constants_1.PASSWORD_MAX_LENGTH),
|
|
31
|
+
__metadata("design:type", String)
|
|
32
|
+
], RequestEmailChangeInput.prototype, "currentPassword", void 0);
|
|
33
|
+
/** POST /auth/email/change/confirm */
|
|
34
|
+
class ConfirmEmailChangeInput {
|
|
35
|
+
token;
|
|
36
|
+
}
|
|
37
|
+
exports.ConfirmEmailChangeInput = ConfirmEmailChangeInput;
|
|
38
|
+
__decorate([
|
|
39
|
+
(0, class_validator_1.IsString)(),
|
|
40
|
+
(0, class_validator_1.MaxLength)(constants_1.RANDOM_TOKEN_INPUT_MAX_LENGTH),
|
|
41
|
+
__metadata("design:type", String)
|
|
42
|
+
], ConfirmEmailChangeInput.prototype, "token", void 0);
|
|
43
|
+
//# sourceMappingURL=email-change.dto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-change.dto.js","sourceRoot":"","sources":["../../../src/features/email-change/email-change.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAkE;AAClE,2CAAmG;AAEnG,8BAA8B;AAC9B,MAAa,uBAAuB;IAElC,QAAQ,CAAS;IAEjB,mGAAmG;IAEnG,eAAe,CAAU;CAC1B;AAPD,0DAOC;AALC;IADC,IAAA,0BAAQ,GAAE;IAAE,IAAA,2BAAS,EAAC,4BAAgB,CAAC;;yDACvB;AAIjB;IADC,IAAA,4BAAU,GAAE;IAAE,IAAA,0BAAQ,GAAE;IAAE,IAAA,2BAAS,EAAC,+BAAmB,CAAC;;gEAChC;AAG3B,sCAAsC;AACtC,MAAa,uBAAuB;IAElC,KAAK,CAAS;CACf;AAHD,0DAGC;AADC;IADC,IAAA,0BAAQ,GAAE;IAAE,IAAA,2BAAS,EAAC,yCAA6B,CAAC;;sDACvC"}
|