@lenne.tech/nest-server 11.41.3 → 11.41.5

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.
Files changed (109) hide show
  1. package/.claude/rules/architecture.md +1 -0
  2. package/.claude/rules/configurable-features.md +1 -0
  3. package/.claude/rules/role-system.md +15 -1
  4. package/.claude/rules/testing.md +16 -4
  5. package/CLAUDE.md +6 -3
  6. package/FRAMEWORK-API.md +4 -1
  7. package/dist/core/common/helpers/graceful-shutdown.helper.js.map +1 -1
  8. package/dist/core/common/helpers/logging.helper.js +2 -0
  9. package/dist/core/common/helpers/logging.helper.js.map +1 -1
  10. package/dist/core/common/helpers/process-diagnostics.helper.js.map +1 -1
  11. package/dist/core/common/interfaces/server-options.interface.d.ts +21 -1
  12. package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.d.ts +2 -2
  13. package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js +46 -6
  14. package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js.map +1 -1
  15. package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.js +7 -1
  16. package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.js.map +1 -1
  17. package/dist/core/modules/api-token/core-api-token.constants.d.ts +6 -0
  18. package/dist/core/modules/api-token/core-api-token.constants.js +11 -0
  19. package/dist/core/modules/api-token/core-api-token.constants.js.map +1 -0
  20. package/dist/core/modules/api-token/core-api-token.decorators.d.ts +1 -0
  21. package/dist/core/modules/api-token/core-api-token.decorators.js +8 -0
  22. package/dist/core/modules/api-token/core-api-token.decorators.js.map +1 -0
  23. package/dist/core/modules/api-token/core-api-token.helpers.d.ts +127 -0
  24. package/dist/core/modules/api-token/core-api-token.helpers.js +398 -0
  25. package/dist/core/modules/api-token/core-api-token.helpers.js.map +1 -0
  26. package/dist/core/modules/api-token/core-api-token.middleware.d.ts +10 -0
  27. package/dist/core/modules/api-token/core-api-token.middleware.js +58 -0
  28. package/dist/core/modules/api-token/core-api-token.middleware.js.map +1 -0
  29. package/dist/core/modules/api-token/core-api-token.model.d.ts +20 -0
  30. package/dist/core/modules/api-token/core-api-token.model.js +199 -0
  31. package/dist/core/modules/api-token/core-api-token.model.js.map +1 -0
  32. package/dist/core/modules/api-token/core-api-token.module.d.ts +11 -0
  33. package/dist/core/modules/api-token/core-api-token.module.js +39 -0
  34. package/dist/core/modules/api-token/core-api-token.module.js.map +1 -0
  35. package/dist/core/modules/api-token/core-api-token.registry.d.ts +9 -0
  36. package/dist/core/modules/api-token/core-api-token.registry.js +17 -0
  37. package/dist/core/modules/api-token/core-api-token.registry.js.map +1 -0
  38. package/dist/core/modules/api-token/core-api-token.service.d.ts +104 -0
  39. package/dist/core/modules/api-token/core-api-token.service.js +550 -0
  40. package/dist/core/modules/api-token/core-api-token.service.js.map +1 -0
  41. package/dist/core/modules/auth/guards/roles.guard.js +17 -1
  42. package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
  43. package/dist/core/modules/better-auth/better-auth-roles.guard.js +12 -2
  44. package/dist/core/modules/better-auth/better-auth-roles.guard.js.map +1 -1
  45. package/dist/core/modules/better-auth/core-better-auth.middleware.js +4 -0
  46. package/dist/core/modules/better-auth/core-better-auth.middleware.js.map +1 -1
  47. package/dist/core/modules/better-auth/core-better-auth.module.d.ts +4 -0
  48. package/dist/core/modules/better-auth/core-better-auth.module.js +18 -0
  49. package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
  50. package/dist/core/modules/migrate/migration-runner.d.ts +1 -0
  51. package/dist/core/modules/migrate/migration-runner.js +3 -0
  52. package/dist/core/modules/migrate/migration-runner.js.map +1 -1
  53. package/dist/core/modules/tenant/core-tenant-guard.registry.d.ts +2 -0
  54. package/dist/core/modules/tenant/core-tenant-guard.registry.js +19 -0
  55. package/dist/core/modules/tenant/core-tenant-guard.registry.js.map +1 -0
  56. package/dist/core/modules/tenant/core-tenant.guard.d.ts +1 -0
  57. package/dist/core/modules/tenant/core-tenant.guard.js +28 -12
  58. package/dist/core/modules/tenant/core-tenant.guard.js.map +1 -1
  59. package/dist/core/modules/tenant/core-tenant.helpers.d.ts +1 -0
  60. package/dist/core/modules/tenant/core-tenant.helpers.js +19 -0
  61. package/dist/core/modules/tenant/core-tenant.helpers.js.map +1 -1
  62. package/dist/core/modules/tenant/core-tenant.module.d.ts +5 -2
  63. package/dist/core/modules/tenant/core-tenant.module.js +8 -0
  64. package/dist/core/modules/tenant/core-tenant.module.js.map +1 -1
  65. package/dist/core/modules/user/core-user.service.js +7 -0
  66. package/dist/core/modules/user/core-user.service.js.map +1 -1
  67. package/dist/core.module.js +6 -0
  68. package/dist/core.module.js.map +1 -1
  69. package/dist/index.d.ts +7 -0
  70. package/dist/index.js +7 -0
  71. package/dist/index.js.map +1 -1
  72. package/dist/tsconfig.build.tsbuildinfo +1 -1
  73. package/docs/REQUEST-LIFECYCLE.md +49 -1
  74. package/docs/security-overrides.md +21 -13
  75. package/migration-guides/11.41.3-to-11.41.4.md +172 -0
  76. package/migration-guides/11.41.4-to-11.41.5.md +144 -0
  77. package/package.json +35 -34
  78. package/src/core/common/helpers/graceful-shutdown.helper.ts +9 -0
  79. package/src/core/common/helpers/logging.helper.ts +7 -0
  80. package/src/core/common/helpers/process-diagnostics.helper.ts +4 -0
  81. package/src/core/common/interfaces/server-options.interface.ts +122 -6
  82. package/src/core/modules/ai/INTEGRATION-CHECKLIST.md +17 -10
  83. package/src/core/modules/ai/README.md +9 -1
  84. package/src/core/modules/ai/helpers/ai-mcp-oauth.helper.ts +74 -7
  85. package/src/core/modules/ai/services/core-ai-mcp-oauth.service.ts +13 -1
  86. package/src/core/modules/api-token/INTEGRATION-CHECKLIST.md +121 -0
  87. package/src/core/modules/api-token/README.md +212 -0
  88. package/src/core/modules/api-token/core-api-token.constants.ts +27 -0
  89. package/src/core/modules/api-token/core-api-token.decorators.ts +29 -0
  90. package/src/core/modules/api-token/core-api-token.helpers.ts +711 -0
  91. package/src/core/modules/api-token/core-api-token.middleware.ts +57 -0
  92. package/src/core/modules/api-token/core-api-token.model.ts +193 -0
  93. package/src/core/modules/api-token/core-api-token.module.ts +48 -0
  94. package/src/core/modules/api-token/core-api-token.registry.ts +53 -0
  95. package/src/core/modules/api-token/core-api-token.service.ts +822 -0
  96. package/src/core/modules/auth/guards/roles.guard.ts +23 -2
  97. package/src/core/modules/better-auth/better-auth-roles.guard.ts +18 -4
  98. package/src/core/modules/better-auth/core-better-auth.middleware.ts +8 -0
  99. package/src/core/modules/better-auth/core-better-auth.module.ts +33 -0
  100. package/src/core/modules/migrate/README.md +15 -7
  101. package/src/core/modules/migrate/migration-runner.ts +9 -0
  102. package/src/core/modules/tenant/README.md +17 -0
  103. package/src/core/modules/tenant/core-tenant-guard.registry.ts +36 -0
  104. package/src/core/modules/tenant/core-tenant.guard.ts +52 -12
  105. package/src/core/modules/tenant/core-tenant.helpers.ts +30 -0
  106. package/src/core/modules/tenant/core-tenant.module.ts +19 -2
  107. package/src/core/modules/user/core-user.service.ts +14 -0
  108. package/src/core.module.ts +12 -0
  109. package/src/index.ts +12 -0
@@ -0,0 +1,57 @@
1
+ import { HttpException, HttpStatus, Injectable, NestMiddleware, Optional, UnauthorizedException } from '@nestjs/common';
2
+ import { NextFunction, Request, Response } from 'express';
3
+
4
+ import { ErrorCode } from '../error-code/error-codes';
5
+ import { getApiTokenConfig, getApiTokenContext, readApiTokenCredentialFromHeaders } from './core-api-token.helpers';
6
+ import { CoreApiTokenService } from './core-api-token.service';
7
+
8
+ /**
9
+ * Authenticates requests that carry an API token or a signed assertion — `Authorization: Bearer …` or
10
+ * `x-api-key: …` — and sets `request.user` (a tenant-token principal, or the owning user of a user
11
+ * token, both carrying the token context).
12
+ *
13
+ * Only credentials with the configured prefix are claimed; Better-Auth sessions, JWTs and legacy tokens
14
+ * pass through untouched (and `CoreBetterAuthMiddleware` in turn leaves prefixed credentials alone).
15
+ *
16
+ * A prefixed credential that does not authenticate is answered with 401 on EVERY route, public ones
17
+ * included — never silently downgraded to anonymous: an integration whose token was revoked must learn
18
+ * that at once, not by missing data. Whether an authenticated token may call a route is decided later by
19
+ * the guards (`@ApiTokenScopes`).
20
+ */
21
+ @Injectable()
22
+ export class CoreApiTokenMiddleware implements NestMiddleware {
23
+ constructor(@Optional() protected readonly apiTokenService?: CoreApiTokenService) {}
24
+
25
+ async use(req: Request & { user?: unknown }, res: Response, next: NextFunction): Promise<void> {
26
+ const config = getApiTokenConfig();
27
+ if (!config.enabled) {
28
+ return next();
29
+ }
30
+
31
+ const credential = readApiTokenCredentialFromHeaders(req.headers, config.prefix);
32
+ if (!credential) {
33
+ return next();
34
+ }
35
+ if (credential === 'conflict' || !this.apiTokenService) {
36
+ throw new UnauthorizedException(ErrorCode.UNAUTHORIZED);
37
+ }
38
+
39
+ const user = await this.apiTokenService.authenticate(credential);
40
+ const context = getApiTokenContext(user);
41
+ if (!user || !context) {
42
+ throw new UnauthorizedException(ErrorCode.UNAUTHORIZED);
43
+ }
44
+
45
+ const limited = await this.apiTokenService.consumeRateLimit(context.tokenId);
46
+ if (limited) {
47
+ res.setHeader('Retry-After', String(limited.retryAfter));
48
+ throw new HttpException(ErrorCode.RATE_LIMIT_EXCEEDED, HttpStatus.TOO_MANY_REQUESTS);
49
+ }
50
+
51
+ this.apiTokenService.touchLastUsed(context.tokenId);
52
+ // An explicit credential header wins over anything implicit (a cookie) — the same priority
53
+ // Better-Auth applies between its Authorization header and its session cookie.
54
+ req.user = user;
55
+ next();
56
+ }
57
+ }
@@ -0,0 +1,193 @@
1
+ import { ObjectType } from '@nestjs/graphql';
2
+ import { Schema } from '@nestjs/mongoose';
3
+
4
+ import { Restricted } from '../../common/decorators/restricted.decorator';
5
+ import { UnifiedField } from '../../common/decorators/unified-field.decorator';
6
+ import { RoleEnum } from '../../common/enums/role.enum';
7
+ import { accessDeniedException } from '../../common/exceptions/access-denied.exception';
8
+ import { CorePersistenceModel } from '../../common/models/core-persistence.model';
9
+ import { ConfigService } from '../../common/services/config.service';
10
+ import { RequestContext } from '../../common/services/request-context.service';
11
+ import { checkRoleAccess } from '../tenant/core-tenant.helpers';
12
+ import { ApiTokenKind } from './core-api-token.constants';
13
+ import { getApiTokenContext, getApiTokenManageRole } from './core-api-token.helpers';
14
+
15
+ /**
16
+ * API token (`apiTokens` config): a USER token or a TENANT token, see {@link ApiTokenKind}.
17
+ *
18
+ * The secret itself is never stored — only its SHA-256 hash (`secretHash`) and the AES-GCM encrypted
19
+ * signing key for assertions (`signingKeyEncrypted`). Both are `select: false` and `@Restricted(S_NO_ONE)`,
20
+ * so neither a plain query nor a response carries them.
21
+ *
22
+ * The tenant field is `tenant`, not `tenantId` — like `CoreTenantMemberModel` — so the tenant plugin
23
+ * does not filter this collection: authentication has to find a token before any tenant is known, and
24
+ * the service scopes every management query explicitly.
25
+ *
26
+ * Projects extend this model to bind a token to their own data (an upload target, an export setting)
27
+ * and pass the subclass to `CoreApiTokenModule.forRoot({ model })` / `CoreModule` overrides.
28
+ */
29
+ @ObjectType({ description: 'API token', isAbstract: true })
30
+ @Restricted(RoleEnum.S_USER)
31
+ @Schema({ timestamps: true })
32
+ export class CoreApiTokenModel extends CorePersistenceModel {
33
+ /** ID of the user who created the token */
34
+ @UnifiedField({
35
+ description: 'ID of the creating user',
36
+ isOptional: true,
37
+ mongoose: { type: String },
38
+ roles: RoleEnum.S_USER,
39
+ })
40
+ createdBy: string = undefined;
41
+
42
+ /** Free-text description */
43
+ @UnifiedField({ description: 'Description', isOptional: true, mongoose: { type: String }, roles: RoleEnum.S_USER })
44
+ description: string = undefined;
45
+
46
+ /** Expiry; `null` = no expiry */
47
+ @UnifiedField({
48
+ description: 'Expiry date',
49
+ isOptional: true,
50
+ mongoose: { default: null, type: Date },
51
+ roles: RoleEnum.S_USER,
52
+ type: Date,
53
+ })
54
+ expiresAt: Date = undefined;
55
+
56
+ /** USER or TENANT */
57
+ @UnifiedField({
58
+ description: 'Token kind',
59
+ mongoose: { enum: Object.values(ApiTokenKind), required: true, type: String },
60
+ roles: RoleEnum.S_USER,
61
+ type: () => String,
62
+ })
63
+ kind: ApiTokenKind = undefined;
64
+
65
+ /** Last successful use (updated at most once per minute and process) */
66
+ @UnifiedField({
67
+ description: 'Last use',
68
+ isOptional: true,
69
+ mongoose: { default: null, type: Date },
70
+ roles: RoleEnum.S_USER,
71
+ type: Date,
72
+ })
73
+ lastUsedAt: Date = undefined;
74
+
75
+ /** USER tokens: the highest tenant role the token may act with (a cap, never a grant) */
76
+ @UnifiedField({
77
+ description: 'Maximum tenant role',
78
+ isOptional: true,
79
+ mongoose: { type: String },
80
+ roles: RoleEnum.S_USER,
81
+ })
82
+ maxTenantRole: string = undefined;
83
+
84
+ /** Display name */
85
+ @UnifiedField({ description: 'Name', mongoose: { required: true, type: String }, roles: RoleEnum.S_USER })
86
+ name: string = undefined;
87
+
88
+ /** Public part of the token, used for the lookup (not a secret) */
89
+ @UnifiedField({
90
+ description: 'Public token id',
91
+ mongoose: { required: true, type: String, unique: true },
92
+ roles: RoleEnum.S_USER,
93
+ })
94
+ publicId: string = undefined;
95
+
96
+ /** Revocation date; a revoked token is refused */
97
+ @UnifiedField({
98
+ description: 'Revocation date',
99
+ isOptional: true,
100
+ mongoose: { default: null, type: Date },
101
+ roles: RoleEnum.S_USER,
102
+ type: Date,
103
+ })
104
+ revokedAt: Date = undefined;
105
+
106
+ /** Scopes (subset of `apiTokens.scopes`) */
107
+ @UnifiedField({
108
+ description: 'Scopes',
109
+ isArray: true,
110
+ mongoose: { default: [], type: [String] },
111
+ roles: RoleEnum.S_USER,
112
+ type: () => String,
113
+ })
114
+ scopes: string[] = undefined;
115
+
116
+ /** SHA-256 of the token secret — never returned */
117
+ @Restricted(RoleEnum.S_NO_ONE)
118
+ @UnifiedField({
119
+ description: 'Secret hash',
120
+ isOptional: true,
121
+ mongoose: { select: false, type: String },
122
+ roles: RoleEnum.S_NO_ONE,
123
+ })
124
+ secretHash: string = undefined;
125
+
126
+ /** AES-256-GCM encrypted signing key for assertions — never returned */
127
+ @Restricted(RoleEnum.S_NO_ONE)
128
+ @UnifiedField({
129
+ description: 'Encrypted signing key',
130
+ isOptional: true,
131
+ mongoose: { select: false, type: String },
132
+ roles: RoleEnum.S_NO_ONE,
133
+ })
134
+ signingKeyEncrypted: string = undefined;
135
+
136
+ /** TENANT tokens: the owning tenant. USER tokens: the one tenant the token is restricted to, if any */
137
+ @UnifiedField({
138
+ description: 'Tenant ID',
139
+ isOptional: true,
140
+ mongoose: { index: true, type: String },
141
+ roles: RoleEnum.S_USER,
142
+ })
143
+ tenant: string = undefined;
144
+
145
+ /** ID of the user who last changed the token */
146
+ @UnifiedField({
147
+ description: 'ID of the last updating user',
148
+ isOptional: true,
149
+ mongoose: { type: String },
150
+ roles: RoleEnum.S_USER,
151
+ })
152
+ updatedBy: string = undefined;
153
+
154
+ /** USER tokens: the owning user */
155
+ @UnifiedField({
156
+ description: 'Owning user ID',
157
+ isOptional: true,
158
+ mongoose: { index: true, type: String },
159
+ roles: RoleEnum.S_USER,
160
+ })
161
+ user: string = undefined;
162
+
163
+ /**
164
+ * Who may see a token object:
165
+ * - its owner (USER tokens),
166
+ * - a platform admin (while `multiTenancy.adminBypass` is on, or without multi-tenancy),
167
+ * - a person holding the manage role in the token's tenant (TENANT tokens).
168
+ *
169
+ * A request authenticated WITH a token never sees token objects — tokens do not manage tokens.
170
+ */
171
+ override securityCheck(user: any, force?: boolean): this {
172
+ if (force) return this;
173
+ if (!user) throw accessDeniedException(user);
174
+ if (getApiTokenContext(user)) throw accessDeniedException(user);
175
+
176
+ if (this.kind === ApiTokenKind.USER && user.id === this.user) return this;
177
+
178
+ const adminBypass = ConfigService.configFastButReadOnly?.multiTenancy?.adminBypass !== false;
179
+ if (adminBypass && user.hasRole?.([RoleEnum.ADMIN])) return this;
180
+
181
+ const context = RequestContext.get();
182
+ if (
183
+ this.kind === ApiTokenKind.TENANT &&
184
+ context?.tenantId === this.tenant &&
185
+ context?.tenantRole &&
186
+ checkRoleAccess([getApiTokenManageRole()], undefined, context.tenantRole)
187
+ ) {
188
+ return this;
189
+ }
190
+
191
+ throw accessDeniedException(user);
192
+ }
193
+ }
@@ -0,0 +1,48 @@
1
+ import { DynamicModule, Global, MiddlewareConsumer, Module, NestModule, Type } from '@nestjs/common';
2
+ import { MongooseModule, SchemaFactory } from '@nestjs/mongoose';
3
+
4
+ import { API_TOKEN_MODEL_TOKEN } from './core-api-token.constants';
5
+ import { CoreApiTokenMiddleware } from './core-api-token.middleware';
6
+ import { CoreApiTokenModel } from './core-api-token.model';
7
+ import { CoreApiTokenService } from './core-api-token.service';
8
+
9
+ /**
10
+ * Options for `CoreApiTokenModule.forRoot()`.
11
+ */
12
+ export interface CoreApiTokenModuleOptions {
13
+ /** Custom token model (must extend CoreApiTokenModel) — e.g. to bind a token to project data */
14
+ model?: Type<CoreApiTokenModel>;
15
+ /** Custom service (must extend CoreApiTokenService) */
16
+ service?: Type<CoreApiTokenService>;
17
+ }
18
+
19
+ /**
20
+ * API tokens (`apiTokens` config): USER tokens and — with multi-tenancy — TENANT tokens.
21
+ *
22
+ * Registered automatically by `CoreModule.forRoot()` when `apiTokens` is configured. Provides the
23
+ * token model, `CoreApiTokenService` (authentication + management) and `CoreApiTokenMiddleware` on all
24
+ * routes. Route access is enforced by the framework's guards (RolesGuard / BetterAuthRolesGuard, plus
25
+ * CoreTenantGuard with multi-tenancy) via `@ApiTokenScopes()`.
26
+ */
27
+ @Global()
28
+ @Module({})
29
+ export class CoreApiTokenModule implements NestModule {
30
+ static forRoot(options: CoreApiTokenModuleOptions = {}): DynamicModule {
31
+ const schema = SchemaFactory.createForClass(options.model || CoreApiTokenModel);
32
+ // Compound indexes for the two management listings; single-field indexes live on the properties.
33
+ schema.index({ kind: 1, tenant: 1 });
34
+ schema.index({ kind: 1, user: 1 });
35
+
36
+ return {
37
+ exports: [CoreApiTokenService],
38
+ global: true,
39
+ imports: [MongooseModule.forFeature([{ name: API_TOKEN_MODEL_TOKEN, schema }])],
40
+ module: CoreApiTokenModule,
41
+ providers: [{ provide: CoreApiTokenService, useClass: options.service || CoreApiTokenService }],
42
+ };
43
+ }
44
+
45
+ configure(consumer: MiddlewareConsumer): void {
46
+ consumer.apply(CoreApiTokenMiddleware).forRoutes('*');
47
+ }
48
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Lets the password-reset paths revoke a user's API tokens WITHOUT importing the optional token
3
+ * module.
4
+ *
5
+ * Two places end a user's sessions on a reset — Better-Auth's `onPasswordReset` hook (IAM, when
6
+ * `betterAuth.emailAndPassword.revokeSessionsOnPasswordReset` is on) and
7
+ * `CoreUserService.resetPassword()` (legacy, always) — and a user token is a session in all but
8
+ * lifetime: left alive, the credential a reset was meant to retire keeps working through it. Both
9
+ * callers live in modules that load whether or not `apiTokens` is configured, so they reach the
10
+ * service through this registry instead of DI: `CoreApiTokenService` fills it while it runs, and
11
+ * without it every call is a no-op.
12
+ *
13
+ * A true leaf: no imports, so it can never be mid-evaluation when a cycle-adjacent file reads it
14
+ * (see `.claude/rules/architecture.md` → "DI Token Placement (SWC-Safe)").
15
+ *
16
+ * @internal Not public API, but required by the shipped better-auth and user modules.
17
+ */
18
+
19
+ /** Who a reset belongs to — any one field is enough; `userId` is the `users` document id. */
20
+ export interface ApiTokenOwnerIdentity {
21
+ email?: string;
22
+ iamId?: string;
23
+ userId?: string;
24
+ }
25
+
26
+ type ApiTokenRevoker = (identity: ApiTokenOwnerIdentity) => Promise<number>;
27
+
28
+ let revoker: ApiTokenRevoker | undefined;
29
+
30
+ /**
31
+ * Register (or, with `undefined`, clear) the function that revokes a user's tokens.
32
+ * Returns a disposer that clears it again only if it is still the registered one.
33
+ *
34
+ * @internal
35
+ */
36
+ export function setApiTokenRevoker(fn: ApiTokenRevoker | undefined): () => void {
37
+ revoker = fn;
38
+ return () => {
39
+ if (revoker === fn) {
40
+ revoker = undefined;
41
+ }
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Revoke every token of a user. Resolves to the number revoked — `0` when the token module is not
47
+ * running, so callers never need to know whether it is.
48
+ *
49
+ * @internal
50
+ */
51
+ export async function revokeApiTokensOfUser(identity: ApiTokenOwnerIdentity): Promise<number> {
52
+ return revoker ? revoker(identity) : 0;
53
+ }