@technomoron/apicore-server 1.0.0-beta.1
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/LICENSE +21 -0
- package/dist/cjs/api-module.cjs +34 -0
- package/dist/cjs/api-module.d.ts +45 -0
- package/dist/cjs/apicore-server.cjs +1561 -0
- package/dist/cjs/apicore-server.d.ts +288 -0
- package/dist/cjs/auth-api/auth-module.cjs +1248 -0
- package/dist/cjs/auth-api/auth-module.d.ts +116 -0
- package/dist/cjs/auth-api/compat-auth-storage.cjs +128 -0
- package/dist/cjs/auth-api/compat-auth-storage.d.ts +57 -0
- package/dist/cjs/auth-api/mem-auth-store.cjs +121 -0
- package/dist/cjs/auth-api/mem-auth-store.d.ts +68 -0
- package/dist/cjs/auth-api/module.cjs +25 -0
- package/dist/cjs/auth-api/module.d.ts +20 -0
- package/dist/cjs/auth-api/schemas.cjs +171 -0
- package/dist/cjs/auth-api/schemas.d.ts +21 -0
- package/dist/cjs/auth-api/sql-auth-store.cjs +179 -0
- package/dist/cjs/auth-api/sql-auth-store.d.ts +87 -0
- package/dist/cjs/auth-api/storage.cjs +102 -0
- package/dist/cjs/auth-api/storage.d.ts +38 -0
- package/dist/cjs/auth-api/types.cjs +2 -0
- package/dist/cjs/auth-api/types.d.ts +34 -0
- package/dist/cjs/auth-api/user-id.cjs +47 -0
- package/dist/cjs/auth-api/user-id.d.ts +5 -0
- package/dist/cjs/auth-cookie-options.cjs +66 -0
- package/dist/cjs/auth-cookie-options.d.ts +13 -0
- package/dist/cjs/base/client-info.cjs +285 -0
- package/dist/cjs/base/client-info.d.ts +27 -0
- package/dist/cjs/base/error-utils.cjs +50 -0
- package/dist/cjs/base/error-utils.d.ts +16 -0
- package/dist/cjs/base/request-utils.cjs +27 -0
- package/dist/cjs/base/request-utils.d.ts +8 -0
- package/dist/cjs/index.cjs +51 -0
- package/dist/cjs/index.d.ts +34 -0
- package/dist/cjs/limiter/auth-rate-limiter.cjs +35 -0
- package/dist/cjs/limiter/auth-rate-limiter.d.ts +12 -0
- package/dist/cjs/limiter/fixed-window.cjs +41 -0
- package/dist/cjs/limiter/fixed-window.d.ts +11 -0
- package/dist/cjs/oauth/base.cjs +7 -0
- package/dist/cjs/oauth/base.d.ts +17 -0
- package/dist/cjs/oauth/memory.cjs +135 -0
- package/dist/cjs/oauth/memory.d.ts +22 -0
- package/dist/cjs/oauth/models.cjs +47 -0
- package/dist/cjs/oauth/models.d.ts +50 -0
- package/dist/cjs/oauth/sequelize.cjs +159 -0
- package/dist/cjs/oauth/sequelize.d.ts +30 -0
- package/dist/cjs/oauth/types.cjs +3 -0
- package/dist/cjs/oauth/types.d.ts +51 -0
- package/dist/cjs/passkey/base.cjs +7 -0
- package/dist/cjs/passkey/base.d.ts +28 -0
- package/dist/cjs/passkey/config.cjs +26 -0
- package/dist/cjs/passkey/config.d.ts +2 -0
- package/dist/cjs/passkey/memory.cjs +123 -0
- package/dist/cjs/passkey/memory.d.ts +34 -0
- package/dist/cjs/passkey/models.cjs +142 -0
- package/dist/cjs/passkey/models.d.ts +34 -0
- package/dist/cjs/passkey/sequelize.cjs +126 -0
- package/dist/cjs/passkey/sequelize.d.ts +42 -0
- package/dist/cjs/passkey/service.cjs +413 -0
- package/dist/cjs/passkey/service.d.ts +21 -0
- package/dist/cjs/passkey/types.cjs +2 -0
- package/dist/cjs/passkey/types.d.ts +84 -0
- package/dist/cjs/sequelize-utils.cjs +56 -0
- package/dist/cjs/sequelize-utils.d.ts +8 -0
- package/dist/cjs/token/base.cjs +120 -0
- package/dist/cjs/token/base.d.ts +46 -0
- package/dist/cjs/token/memory.cjs +234 -0
- package/dist/cjs/token/memory.d.ts +29 -0
- package/dist/cjs/token/sequelize.cjs +400 -0
- package/dist/cjs/token/sequelize.d.ts +58 -0
- package/dist/cjs/token/types.cjs +2 -0
- package/dist/cjs/token/types.d.ts +34 -0
- package/dist/cjs/upload/memory.cjs +92 -0
- package/dist/cjs/upload/memory.d.ts +17 -0
- package/dist/cjs/upload/tus-module.cjs +270 -0
- package/dist/cjs/upload/tus-module.d.ts +38 -0
- package/dist/cjs/upload/types.cjs +2 -0
- package/dist/cjs/upload/types.d.ts +28 -0
- package/dist/cjs/user/base.cjs +53 -0
- package/dist/cjs/user/base.d.ts +36 -0
- package/dist/cjs/user/memory.cjs +194 -0
- package/dist/cjs/user/memory.d.ts +37 -0
- package/dist/cjs/user/sequelize.cjs +194 -0
- package/dist/cjs/user/sequelize.d.ts +46 -0
- package/dist/cjs/user/types.cjs +2 -0
- package/dist/cjs/user/types.d.ts +11 -0
- package/dist/esm/api-module.d.ts +45 -0
- package/dist/esm/api-module.js +30 -0
- package/dist/esm/apicore-server.d.ts +288 -0
- package/dist/esm/apicore-server.js +1552 -0
- package/dist/esm/auth-api/auth-module.d.ts +116 -0
- package/dist/esm/auth-api/auth-module.js +1246 -0
- package/dist/esm/auth-api/compat-auth-storage.d.ts +57 -0
- package/dist/esm/auth-api/compat-auth-storage.js +124 -0
- package/dist/esm/auth-api/mem-auth-store.d.ts +68 -0
- package/dist/esm/auth-api/mem-auth-store.js +117 -0
- package/dist/esm/auth-api/module.d.ts +20 -0
- package/dist/esm/auth-api/module.js +21 -0
- package/dist/esm/auth-api/schemas.d.ts +21 -0
- package/dist/esm/auth-api/schemas.js +168 -0
- package/dist/esm/auth-api/sql-auth-store.d.ts +87 -0
- package/dist/esm/auth-api/sql-auth-store.js +175 -0
- package/dist/esm/auth-api/storage.d.ts +38 -0
- package/dist/esm/auth-api/storage.js +98 -0
- package/dist/esm/auth-api/types.d.ts +34 -0
- package/dist/esm/auth-api/types.js +1 -0
- package/dist/esm/auth-api/user-id.d.ts +5 -0
- package/dist/esm/auth-api/user-id.js +41 -0
- package/dist/esm/auth-cookie-options.d.ts +13 -0
- package/dist/esm/auth-cookie-options.js +63 -0
- package/dist/esm/base/client-info.d.ts +27 -0
- package/dist/esm/base/client-info.js +282 -0
- package/dist/esm/base/error-utils.d.ts +16 -0
- package/dist/esm/base/error-utils.js +44 -0
- package/dist/esm/base/request-utils.d.ts +8 -0
- package/dist/esm/base/request-utils.js +23 -0
- package/dist/esm/index.d.ts +34 -0
- package/dist/esm/index.js +21 -0
- package/dist/esm/limiter/auth-rate-limiter.d.ts +12 -0
- package/dist/esm/limiter/auth-rate-limiter.js +32 -0
- package/dist/esm/limiter/fixed-window.d.ts +11 -0
- package/dist/esm/limiter/fixed-window.js +37 -0
- package/dist/esm/oauth/base.d.ts +17 -0
- package/dist/esm/oauth/base.js +3 -0
- package/dist/esm/oauth/memory.d.ts +22 -0
- package/dist/esm/oauth/memory.js +128 -0
- package/dist/esm/oauth/models.d.ts +50 -0
- package/dist/esm/oauth/models.js +38 -0
- package/dist/esm/oauth/sequelize.d.ts +30 -0
- package/dist/esm/oauth/sequelize.js +148 -0
- package/dist/esm/oauth/types.d.ts +51 -0
- package/dist/esm/oauth/types.js +2 -0
- package/dist/esm/passkey/base.d.ts +28 -0
- package/dist/esm/passkey/base.js +3 -0
- package/dist/esm/passkey/config.d.ts +2 -0
- package/dist/esm/passkey/config.js +23 -0
- package/dist/esm/passkey/memory.d.ts +34 -0
- package/dist/esm/passkey/memory.js +119 -0
- package/dist/esm/passkey/models.d.ts +34 -0
- package/dist/esm/passkey/models.js +135 -0
- package/dist/esm/passkey/sequelize.d.ts +42 -0
- package/dist/esm/passkey/sequelize.js +122 -0
- package/dist/esm/passkey/service.d.ts +21 -0
- package/dist/esm/passkey/service.js +376 -0
- package/dist/esm/passkey/types.d.ts +84 -0
- package/dist/esm/passkey/types.js +1 -0
- package/dist/esm/sequelize-utils.d.ts +8 -0
- package/dist/esm/sequelize-utils.js +47 -0
- package/dist/esm/token/base.d.ts +46 -0
- package/dist/esm/token/base.js +113 -0
- package/dist/esm/token/memory.d.ts +29 -0
- package/dist/esm/token/memory.js +230 -0
- package/dist/esm/token/sequelize.d.ts +58 -0
- package/dist/esm/token/sequelize.js +396 -0
- package/dist/esm/token/types.d.ts +34 -0
- package/dist/esm/token/types.js +1 -0
- package/dist/esm/upload/memory.d.ts +17 -0
- package/dist/esm/upload/memory.js +86 -0
- package/dist/esm/upload/tus-module.d.ts +38 -0
- package/dist/esm/upload/tus-module.js +266 -0
- package/dist/esm/upload/types.d.ts +28 -0
- package/dist/esm/upload/types.js +1 -0
- package/dist/esm/user/base.d.ts +36 -0
- package/dist/esm/user/base.js +46 -0
- package/dist/esm/user/memory.d.ts +37 -0
- package/dist/esm/user/memory.js +190 -0
- package/dist/esm/user/sequelize.d.ts +46 -0
- package/dist/esm/user/sequelize.js +188 -0
- package/dist/esm/user/types.d.ts +11 -0
- package/dist/esm/user/types.js +1 -0
- package/docs/swagger/openapi.json +2162 -0
- package/package.json +131 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { PasskeyService } from '../passkey/service.js';
|
|
2
|
+
import type { AuthAdapter, AuthIdentifier } from './types.js';
|
|
3
|
+
import type { OAuthStore } from '../oauth/base.js';
|
|
4
|
+
import type { AuthCodeData, AuthCodeRequest, OAuthClient } from '../oauth/types.js';
|
|
5
|
+
import type { PasskeyStore } from '../passkey/base.js';
|
|
6
|
+
import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyServiceConfig, StoredPasskeyCredential, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
|
|
7
|
+
import type { TokenStore } from '../token/base.js';
|
|
8
|
+
import type { Token } from '../token/types.js';
|
|
9
|
+
import type { UserStore } from '../user/base.js';
|
|
10
|
+
interface PasskeyAdapterOptions {
|
|
11
|
+
store: PasskeyStore;
|
|
12
|
+
config: PasskeyServiceConfig;
|
|
13
|
+
}
|
|
14
|
+
export interface AuthAdapterOptions<UserRow, PublicUser> {
|
|
15
|
+
userStore: UserStore<UserRow, PublicUser>;
|
|
16
|
+
tokenStore: TokenStore;
|
|
17
|
+
passkeys?: PasskeyAdapterOptions | PasskeyService;
|
|
18
|
+
oauthStore?: OAuthStore;
|
|
19
|
+
canImpersonate?: (params: {
|
|
20
|
+
realUserId: AuthIdentifier;
|
|
21
|
+
effectiveUserId: AuthIdentifier;
|
|
22
|
+
}) => boolean | Promise<boolean>;
|
|
23
|
+
}
|
|
24
|
+
export declare class CompositeAuthAdapter<UserRow, PublicUser> implements AuthAdapter<UserRow, PublicUser> {
|
|
25
|
+
private readonly userStore;
|
|
26
|
+
private readonly tokenStore;
|
|
27
|
+
private readonly oauthStore?;
|
|
28
|
+
private readonly passkeyService?;
|
|
29
|
+
private readonly canImpersonateFn?;
|
|
30
|
+
constructor(options: AuthAdapterOptions<UserRow, PublicUser>);
|
|
31
|
+
getUser(identifier: AuthIdentifier): Promise<UserRow | null>;
|
|
32
|
+
getUserPasswordHash(user: UserRow): string;
|
|
33
|
+
getUserId(user: UserRow): AuthIdentifier;
|
|
34
|
+
filterUser(user: UserRow): PublicUser;
|
|
35
|
+
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
36
|
+
storeToken(data: Token): Promise<void>;
|
|
37
|
+
getToken(query: Partial<Token>, opts?: {
|
|
38
|
+
includeExpired?: boolean;
|
|
39
|
+
}): Promise<Token | null>;
|
|
40
|
+
deleteToken(query: Partial<Token>): Promise<number>;
|
|
41
|
+
updateToken(updates: Partial<Token> & {
|
|
42
|
+
refreshToken: string;
|
|
43
|
+
}): Promise<boolean>;
|
|
44
|
+
createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
|
|
45
|
+
verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
|
|
46
|
+
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
47
|
+
deletePasskeyCredential(credentialId: Buffer | string): Promise<boolean>;
|
|
48
|
+
getClient(clientId: string): Promise<OAuthClient | null>;
|
|
49
|
+
verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
|
|
50
|
+
createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
|
|
51
|
+
consumeAuthCode(code: string, clientId: string): Promise<AuthCodeData | null>;
|
|
52
|
+
canImpersonate(params: {
|
|
53
|
+
realUserId: AuthIdentifier;
|
|
54
|
+
effectiveUserId: AuthIdentifier;
|
|
55
|
+
}): Promise<boolean>;
|
|
56
|
+
}
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { PasskeyService } from '../passkey/service.js';
|
|
3
|
+
export class CompositeAuthAdapter {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.userStore = options.userStore;
|
|
6
|
+
this.tokenStore = options.tokenStore;
|
|
7
|
+
this.oauthStore = options.oauthStore;
|
|
8
|
+
this.canImpersonateFn = options.canImpersonate;
|
|
9
|
+
if (options.passkeys instanceof PasskeyService) {
|
|
10
|
+
this.passkeyService = options.passkeys;
|
|
11
|
+
}
|
|
12
|
+
else if (options.passkeys) {
|
|
13
|
+
this.passkeyService = new PasskeyService(options.passkeys.config, options.passkeys.store);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async getUser(identifier) {
|
|
17
|
+
return this.userStore.findUser(identifier);
|
|
18
|
+
}
|
|
19
|
+
getUserPasswordHash(user) {
|
|
20
|
+
return this.userStore.getPasswordHash(user) ?? '';
|
|
21
|
+
}
|
|
22
|
+
getUserId(user) {
|
|
23
|
+
return this.userStore.getUserId(user);
|
|
24
|
+
}
|
|
25
|
+
filterUser(user) {
|
|
26
|
+
return this.userStore.toPublic(user);
|
|
27
|
+
}
|
|
28
|
+
async verifyPassword(password, hash) {
|
|
29
|
+
return this.userStore.verifyPassword(password, hash);
|
|
30
|
+
}
|
|
31
|
+
async storeToken(data) {
|
|
32
|
+
return this.tokenStore.save(data);
|
|
33
|
+
}
|
|
34
|
+
async getToken(query, opts) {
|
|
35
|
+
return this.tokenStore.get(query, opts);
|
|
36
|
+
}
|
|
37
|
+
async deleteToken(query) {
|
|
38
|
+
return this.tokenStore.delete(query);
|
|
39
|
+
}
|
|
40
|
+
async updateToken(updates) {
|
|
41
|
+
return this.tokenStore.update(updates);
|
|
42
|
+
}
|
|
43
|
+
async createPasskeyChallenge(params) {
|
|
44
|
+
if (!this.passkeyService) {
|
|
45
|
+
throw new Error('Passkey storage is not configured');
|
|
46
|
+
}
|
|
47
|
+
return this.passkeyService.createChallenge(params);
|
|
48
|
+
}
|
|
49
|
+
async verifyPasskeyResponse(params) {
|
|
50
|
+
if (!this.passkeyService) {
|
|
51
|
+
throw new Error('Passkey storage is not configured');
|
|
52
|
+
}
|
|
53
|
+
return this.passkeyService.verifyResponse(params);
|
|
54
|
+
}
|
|
55
|
+
async listUserCredentials(userId) {
|
|
56
|
+
if (!this.passkeyService) {
|
|
57
|
+
throw new Error('Passkey storage is not configured');
|
|
58
|
+
}
|
|
59
|
+
return this.passkeyService.listUserCredentials(userId);
|
|
60
|
+
}
|
|
61
|
+
async deletePasskeyCredential(credentialId) {
|
|
62
|
+
if (!this.passkeyService) {
|
|
63
|
+
throw new Error('Passkey storage is not configured');
|
|
64
|
+
}
|
|
65
|
+
return this.passkeyService.deleteCredential(credentialId);
|
|
66
|
+
}
|
|
67
|
+
async getClient(clientId) {
|
|
68
|
+
if (!this.oauthStore) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return this.oauthStore.getClient(clientId);
|
|
72
|
+
}
|
|
73
|
+
async verifyClientSecret(client, clientSecret) {
|
|
74
|
+
if (!this.oauthStore) {
|
|
75
|
+
throw new Error('OAuth storage is not configured');
|
|
76
|
+
}
|
|
77
|
+
return this.oauthStore.verifyClientSecret(client.clientId, clientSecret);
|
|
78
|
+
}
|
|
79
|
+
async createAuthCode(request) {
|
|
80
|
+
if (!this.oauthStore) {
|
|
81
|
+
throw new Error('OAuth storage is not configured');
|
|
82
|
+
}
|
|
83
|
+
const expiresAt = new Date(Date.now() + (request.expiresInSeconds ?? 300) * 1000);
|
|
84
|
+
const code = request.code ?? randomUUID();
|
|
85
|
+
await this.oauthStore.createAuthCode({
|
|
86
|
+
code,
|
|
87
|
+
clientId: request.clientId,
|
|
88
|
+
userId: request.userId,
|
|
89
|
+
scope: request.scope,
|
|
90
|
+
redirectUri: request.redirectUri,
|
|
91
|
+
codeChallenge: request.codeChallenge,
|
|
92
|
+
codeChallengeMethod: request.codeChallengeMethod,
|
|
93
|
+
expiresAt,
|
|
94
|
+
metadata: request.metadata
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
code,
|
|
98
|
+
clientId: request.clientId,
|
|
99
|
+
userId: request.userId,
|
|
100
|
+
redirectUri: request.redirectUri,
|
|
101
|
+
scope: request.scope ?? [],
|
|
102
|
+
codeChallenge: request.codeChallenge,
|
|
103
|
+
codeChallengeMethod: request.codeChallengeMethod,
|
|
104
|
+
expiresAt,
|
|
105
|
+
metadata: request.metadata
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async consumeAuthCode(code, clientId) {
|
|
109
|
+
if (!this.oauthStore) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const consumed = await this.oauthStore.consumeAuthCode(code, clientId);
|
|
113
|
+
if (!consumed) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return consumed;
|
|
117
|
+
}
|
|
118
|
+
async canImpersonate(params) {
|
|
119
|
+
if (this.canImpersonateFn) {
|
|
120
|
+
return !!(await this.canImpersonateFn(params));
|
|
121
|
+
}
|
|
122
|
+
return String(params.realUserId) === String(params.effectiveUserId);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { MemoryOAuthStore } from '../oauth/memory.js';
|
|
2
|
+
import { MemoryPasskeyStore } from '../passkey/memory.js';
|
|
3
|
+
import { TokenStore } from '../token/base.js';
|
|
4
|
+
import { MemoryUserStore } from '../user/memory.js';
|
|
5
|
+
import type { AuthAdapter, AuthIdentifier } from './types.js';
|
|
6
|
+
import type { MemoryOAuthStoreOptions } from '../oauth/memory.js';
|
|
7
|
+
import type { AuthCodeData, AuthCodeRequest, OAuthClient } from '../oauth/types.js';
|
|
8
|
+
import type { MemoryPasskeyStoreOptions } from '../passkey/memory.js';
|
|
9
|
+
import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyServiceConfig, PasskeyUserDescriptor, StoredPasskeyCredential, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
|
|
10
|
+
import type { Token } from '../token/types.js';
|
|
11
|
+
import type { MemoryUserAttributes, MemoryPublicUser, MemoryUserStoreOptions } from '../user/memory.js';
|
|
12
|
+
interface PasskeyOptions extends Partial<PasskeyServiceConfig> {
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface MemAuthStoreParams<UserAttributes extends MemoryUserAttributes = MemoryUserAttributes, PublicUserShape extends MemoryPublicUser<UserAttributes> = MemoryPublicUser<UserAttributes>> extends Omit<MemoryUserStoreOptions<UserAttributes, PublicUserShape>, 'hasher'>, MemoryOAuthStoreOptions, Partial<MemoryPasskeyStoreOptions> {
|
|
16
|
+
bcryptRounds?: number;
|
|
17
|
+
passwordPepper?: string;
|
|
18
|
+
publicUserMapper?: (user: UserAttributes) => PublicUserShape;
|
|
19
|
+
passkeyUserMapper?: (user: UserAttributes) => PasskeyUserDescriptor;
|
|
20
|
+
passkeys?: false | PasskeyOptions;
|
|
21
|
+
canImpersonate?: (params: {
|
|
22
|
+
realUserId: AuthIdentifier;
|
|
23
|
+
effectiveUserId: AuthIdentifier;
|
|
24
|
+
}) => boolean | Promise<boolean>;
|
|
25
|
+
tokenStore?: TokenStore;
|
|
26
|
+
}
|
|
27
|
+
export declare class MemAuthStore<UserAttributes extends MemoryUserAttributes = MemoryUserAttributes, PublicUserShape extends MemoryPublicUser<UserAttributes> = MemoryPublicUser<UserAttributes>> implements AuthAdapter<UserAttributes, PublicUserShape> {
|
|
28
|
+
readonly userStore: MemoryUserStore<UserAttributes, PublicUserShape>;
|
|
29
|
+
readonly tokenStore: TokenStore;
|
|
30
|
+
readonly passkeyStore?: MemoryPasskeyStore;
|
|
31
|
+
readonly oauthStore: MemoryOAuthStore;
|
|
32
|
+
private readonly adapter;
|
|
33
|
+
constructor(params?: MemAuthStoreParams<UserAttributes, PublicUserShape>);
|
|
34
|
+
initialise(): Promise<void>;
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
getUser(identifier: AuthIdentifier): Promise<UserAttributes | null>;
|
|
37
|
+
getUserPasswordHash(user: UserAttributes): string;
|
|
38
|
+
getUserId(user: UserAttributes): AuthIdentifier;
|
|
39
|
+
filterUser(user: UserAttributes): PublicUserShape;
|
|
40
|
+
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
41
|
+
storeToken(data: Token): Promise<void>;
|
|
42
|
+
getToken(query: Partial<Token> & {
|
|
43
|
+
userId?: AuthIdentifier;
|
|
44
|
+
ruid?: AuthIdentifier;
|
|
45
|
+
}, opts?: {
|
|
46
|
+
includeExpired?: boolean;
|
|
47
|
+
}): Promise<Token | null>;
|
|
48
|
+
deleteToken(query: Partial<Token> & {
|
|
49
|
+
userId?: AuthIdentifier;
|
|
50
|
+
ruid?: AuthIdentifier;
|
|
51
|
+
}): Promise<number>;
|
|
52
|
+
updateToken(updates: Partial<Token> & {
|
|
53
|
+
refreshToken: string;
|
|
54
|
+
}): Promise<boolean>;
|
|
55
|
+
createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
|
|
56
|
+
verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
|
|
57
|
+
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
58
|
+
deletePasskeyCredential(credentialId: Buffer | string): Promise<boolean>;
|
|
59
|
+
getClient(clientId: string): Promise<OAuthClient | null>;
|
|
60
|
+
verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
|
|
61
|
+
createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
|
|
62
|
+
consumeAuthCode(code: string, clientId: string): Promise<AuthCodeData | null>;
|
|
63
|
+
canImpersonate(params: {
|
|
64
|
+
realUserId: AuthIdentifier;
|
|
65
|
+
effectiveUserId: AuthIdentifier;
|
|
66
|
+
}): Promise<boolean>;
|
|
67
|
+
}
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { MemoryOAuthStore } from '../oauth/memory.js';
|
|
2
|
+
import { normalizePasskeyConfig } from '../passkey/config.js';
|
|
3
|
+
import { MemoryPasskeyStore } from '../passkey/memory.js';
|
|
4
|
+
import { MemoryTokenStore } from '../token/memory.js';
|
|
5
|
+
import { MemoryUserStore } from '../user/memory.js';
|
|
6
|
+
import { CompositeAuthAdapter } from './compat-auth-storage.js';
|
|
7
|
+
import { toOptionalStringId } from './user-id.js';
|
|
8
|
+
export class MemAuthStore {
|
|
9
|
+
constructor(params = {}) {
|
|
10
|
+
this.userStore = new MemoryUserStore({
|
|
11
|
+
toPublic: params.publicUserMapper ?? params.toPublic,
|
|
12
|
+
userIdFactory: params.userIdFactory,
|
|
13
|
+
startingUserId: params.startingUserId,
|
|
14
|
+
bcryptRounds: params.bcryptRounds,
|
|
15
|
+
bcryptPepper: params.passwordPepper
|
|
16
|
+
});
|
|
17
|
+
this.tokenStore = params.tokenStore ?? new MemoryTokenStore();
|
|
18
|
+
this.oauthStore = new MemoryOAuthStore({ bcryptRounds: params.bcryptRounds });
|
|
19
|
+
let passkeyStore;
|
|
20
|
+
let passkeyConfig;
|
|
21
|
+
if (params.passkeys !== false) {
|
|
22
|
+
passkeyConfig = normalizePasskeyConfig(params.passkeys ?? {});
|
|
23
|
+
const resolveUser = async (lookup) => {
|
|
24
|
+
const found = await this.userStore.findUser(lookup.userId ?? lookup.login ?? '');
|
|
25
|
+
if (!found) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const mapper = params.passkeyUserMapper ??
|
|
29
|
+
((user) => ({
|
|
30
|
+
id: user.user_id,
|
|
31
|
+
login: user.login,
|
|
32
|
+
displayName: user.login
|
|
33
|
+
}));
|
|
34
|
+
return mapper(found);
|
|
35
|
+
};
|
|
36
|
+
passkeyStore = new MemoryPasskeyStore({ resolveUser });
|
|
37
|
+
this.passkeyStore = passkeyStore;
|
|
38
|
+
}
|
|
39
|
+
this.adapter = new CompositeAuthAdapter({
|
|
40
|
+
userStore: this.userStore,
|
|
41
|
+
tokenStore: this.tokenStore,
|
|
42
|
+
passkeys: passkeyStore && passkeyConfig ? { store: passkeyStore, config: passkeyConfig } : undefined,
|
|
43
|
+
oauthStore: this.oauthStore,
|
|
44
|
+
canImpersonate: params.canImpersonate
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async initialise() {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
async close() {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
async getUser(identifier) {
|
|
54
|
+
return this.adapter.getUser(identifier);
|
|
55
|
+
}
|
|
56
|
+
getUserPasswordHash(user) {
|
|
57
|
+
return this.adapter.getUserPasswordHash(user);
|
|
58
|
+
}
|
|
59
|
+
getUserId(user) {
|
|
60
|
+
return this.adapter.getUserId(user);
|
|
61
|
+
}
|
|
62
|
+
filterUser(user) {
|
|
63
|
+
return this.adapter.filterUser(user);
|
|
64
|
+
}
|
|
65
|
+
async verifyPassword(password, hash) {
|
|
66
|
+
return this.adapter.verifyPassword(password, hash);
|
|
67
|
+
}
|
|
68
|
+
async storeToken(data) {
|
|
69
|
+
return this.adapter.storeToken(data);
|
|
70
|
+
}
|
|
71
|
+
async getToken(query, opts) {
|
|
72
|
+
const normalized = {
|
|
73
|
+
...query,
|
|
74
|
+
userId: toOptionalStringId(query.userId),
|
|
75
|
+
ruid: toOptionalStringId(query.ruid)
|
|
76
|
+
};
|
|
77
|
+
return this.adapter.getToken(normalized, opts);
|
|
78
|
+
}
|
|
79
|
+
async deleteToken(query) {
|
|
80
|
+
const normalized = {
|
|
81
|
+
...query,
|
|
82
|
+
userId: toOptionalStringId(query.userId),
|
|
83
|
+
ruid: toOptionalStringId(query.ruid)
|
|
84
|
+
};
|
|
85
|
+
return this.adapter.deleteToken(normalized);
|
|
86
|
+
}
|
|
87
|
+
async updateToken(updates) {
|
|
88
|
+
return this.adapter.updateToken(updates);
|
|
89
|
+
}
|
|
90
|
+
async createPasskeyChallenge(params) {
|
|
91
|
+
return this.adapter.createPasskeyChallenge(params);
|
|
92
|
+
}
|
|
93
|
+
async verifyPasskeyResponse(params) {
|
|
94
|
+
return this.adapter.verifyPasskeyResponse(params);
|
|
95
|
+
}
|
|
96
|
+
async listUserCredentials(userId) {
|
|
97
|
+
return this.adapter.listUserCredentials(userId);
|
|
98
|
+
}
|
|
99
|
+
async deletePasskeyCredential(credentialId) {
|
|
100
|
+
return this.adapter.deletePasskeyCredential(credentialId);
|
|
101
|
+
}
|
|
102
|
+
async getClient(clientId) {
|
|
103
|
+
return this.adapter.getClient(clientId);
|
|
104
|
+
}
|
|
105
|
+
async verifyClientSecret(client, clientSecret) {
|
|
106
|
+
return this.adapter.verifyClientSecret(client, clientSecret);
|
|
107
|
+
}
|
|
108
|
+
async createAuthCode(request) {
|
|
109
|
+
return this.adapter.createAuthCode(request);
|
|
110
|
+
}
|
|
111
|
+
async consumeAuthCode(code, clientId) {
|
|
112
|
+
return this.adapter.consumeAuthCode(code, clientId);
|
|
113
|
+
}
|
|
114
|
+
async canImpersonate(params) {
|
|
115
|
+
return this.adapter.canImpersonate(params);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ApiModule } from '../api-module.js';
|
|
2
|
+
import type { ApiRequest } from '../apicore-server.js';
|
|
3
|
+
import type { Token, TokenPair } from '../token/types.js';
|
|
4
|
+
export interface AuthProviderModule<UserRow = unknown> {
|
|
5
|
+
readonly moduleType: 'auth';
|
|
6
|
+
readonly namespace: string;
|
|
7
|
+
issueTokens(apiReq: ApiRequest, user: UserRow, metadata?: Partial<Token> & {
|
|
8
|
+
expires?: Date;
|
|
9
|
+
}): Promise<TokenPair>;
|
|
10
|
+
}
|
|
11
|
+
export declare abstract class BaseAuthModule<UserRow = unknown> extends ApiModule implements AuthProviderModule<UserRow> {
|
|
12
|
+
readonly moduleType: "auth";
|
|
13
|
+
protected constructor(opts: {
|
|
14
|
+
namespace: string;
|
|
15
|
+
});
|
|
16
|
+
abstract issueTokens(apiReq: ApiRequest, user: UserRow, metadata?: Partial<Token> & {
|
|
17
|
+
expires?: Date;
|
|
18
|
+
}): Promise<TokenPair>;
|
|
19
|
+
}
|
|
20
|
+
export declare const nullAuthModule: AuthProviderModule<unknown>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ApiModule } from '../api-module.js';
|
|
2
|
+
// Handy base that you can extend when wiring a real auth module. Subclasses
|
|
3
|
+
// must supply a namespace via the constructor and implement token issuance.
|
|
4
|
+
export class BaseAuthModule extends ApiModule {
|
|
5
|
+
constructor(opts) {
|
|
6
|
+
super(opts);
|
|
7
|
+
this.moduleType = 'auth';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
class NullAuthModule extends BaseAuthModule {
|
|
11
|
+
constructor() {
|
|
12
|
+
super({ namespace: '__null__' });
|
|
13
|
+
}
|
|
14
|
+
async issueTokens(apiReq, user, metadata) {
|
|
15
|
+
void apiReq;
|
|
16
|
+
void user;
|
|
17
|
+
void metadata;
|
|
18
|
+
throw new Error('Auth module not configured. Inject a real auth module before issuing tokens.');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export const nullAuthModule = new NullAuthModule();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema definitions for auth module routes.
|
|
3
|
+
* These are the runtime validation source-of-truth; TypeScript interfaces
|
|
4
|
+
* in auth-module.ts remain for handler-internal typing only.
|
|
5
|
+
*/
|
|
6
|
+
/** Loose JSON Schema object type used for Fastify route schema definitions. */
|
|
7
|
+
type JsonSchema = Record<string, unknown>;
|
|
8
|
+
export declare const loginBodySchema: JsonSchema;
|
|
9
|
+
export declare const refreshBodySchema: JsonSchema;
|
|
10
|
+
export declare const logoutBodySchema: JsonSchema;
|
|
11
|
+
export declare const whoamiBodySchema: JsonSchema;
|
|
12
|
+
export declare const passkeyChallengeBodySchema: JsonSchema;
|
|
13
|
+
export declare const passkeyVerifyBodySchema: JsonSchema;
|
|
14
|
+
export declare const passkeyCredentialParamsSchema: JsonSchema;
|
|
15
|
+
export declare const impersonateBodySchema: JsonSchema;
|
|
16
|
+
export declare const deleteImpersonationQuerySchema: JsonSchema;
|
|
17
|
+
export declare const oauthProviderParamsSchema: JsonSchema;
|
|
18
|
+
export declare const oauthStartBodySchema: JsonSchema;
|
|
19
|
+
export declare const oauthAuthorizeBodySchema: JsonSchema;
|
|
20
|
+
export declare const oauthTokenBodySchema: JsonSchema;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema definitions for auth module routes.
|
|
3
|
+
* These are the runtime validation source-of-truth; TypeScript interfaces
|
|
4
|
+
* in auth-module.ts remain for handler-internal typing only.
|
|
5
|
+
*/
|
|
6
|
+
/* ------------------------------------------------------------------ */
|
|
7
|
+
/* Shared fragments */
|
|
8
|
+
/* ------------------------------------------------------------------ */
|
|
9
|
+
const tokenMetadataProperties = {
|
|
10
|
+
domain: { type: 'string' },
|
|
11
|
+
fingerprint: { type: 'string' },
|
|
12
|
+
label: { type: 'string' },
|
|
13
|
+
browser: { type: 'string' },
|
|
14
|
+
device: { type: 'string' },
|
|
15
|
+
ip: { type: 'string' },
|
|
16
|
+
os: { type: 'string' }
|
|
17
|
+
};
|
|
18
|
+
const keepSessionProperty = {
|
|
19
|
+
keepSession: { type: ['boolean', 'number', 'string'] }
|
|
20
|
+
};
|
|
21
|
+
function authIdentifierProperty(name) {
|
|
22
|
+
return { [name]: { type: ['string', 'number'] } };
|
|
23
|
+
}
|
|
24
|
+
/* ------------------------------------------------------------------ */
|
|
25
|
+
/* Auth route schemas */
|
|
26
|
+
/* ------------------------------------------------------------------ */
|
|
27
|
+
export const loginBodySchema = {
|
|
28
|
+
type: 'object',
|
|
29
|
+
required: ['login', 'password'],
|
|
30
|
+
properties: {
|
|
31
|
+
login: { type: 'string', minLength: 1 },
|
|
32
|
+
password: { type: 'string', minLength: 1 },
|
|
33
|
+
...tokenMetadataProperties,
|
|
34
|
+
...keepSessionProperty
|
|
35
|
+
},
|
|
36
|
+
additionalProperties: true
|
|
37
|
+
};
|
|
38
|
+
export const refreshBodySchema = {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
refreshToken: { type: 'string' },
|
|
42
|
+
domain: { type: 'string' },
|
|
43
|
+
fingerprint: { type: 'string' },
|
|
44
|
+
label: { type: 'string' },
|
|
45
|
+
...keepSessionProperty
|
|
46
|
+
},
|
|
47
|
+
additionalProperties: false
|
|
48
|
+
};
|
|
49
|
+
export const logoutBodySchema = {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
token: { type: 'string' },
|
|
53
|
+
refreshToken: { type: 'string' }
|
|
54
|
+
},
|
|
55
|
+
additionalProperties: false
|
|
56
|
+
};
|
|
57
|
+
export const whoamiBodySchema = {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
refreshToken: { type: 'string' },
|
|
61
|
+
refresh: { type: 'boolean' }
|
|
62
|
+
},
|
|
63
|
+
additionalProperties: false
|
|
64
|
+
};
|
|
65
|
+
/* ------------------------------------------------------------------ */
|
|
66
|
+
/* Passkey schemas */
|
|
67
|
+
/* ------------------------------------------------------------------ */
|
|
68
|
+
export const passkeyChallengeBodySchema = {
|
|
69
|
+
type: 'object',
|
|
70
|
+
required: ['action'],
|
|
71
|
+
properties: {
|
|
72
|
+
action: { type: 'string', enum: ['register', 'authenticate'] },
|
|
73
|
+
login: { type: 'string' },
|
|
74
|
+
...authIdentifierProperty('userId')
|
|
75
|
+
},
|
|
76
|
+
additionalProperties: false
|
|
77
|
+
};
|
|
78
|
+
export const passkeyVerifyBodySchema = {
|
|
79
|
+
type: 'object',
|
|
80
|
+
required: ['expectedChallenge', 'response'],
|
|
81
|
+
properties: {
|
|
82
|
+
expectedChallenge: { type: 'string' },
|
|
83
|
+
response: { type: 'object' },
|
|
84
|
+
login: { type: 'string' },
|
|
85
|
+
...authIdentifierProperty('userId'),
|
|
86
|
+
userAgent: { type: 'string' },
|
|
87
|
+
...tokenMetadataProperties,
|
|
88
|
+
...keepSessionProperty
|
|
89
|
+
},
|
|
90
|
+
additionalProperties: true
|
|
91
|
+
};
|
|
92
|
+
export const passkeyCredentialParamsSchema = {
|
|
93
|
+
type: 'object',
|
|
94
|
+
required: ['credentialId'],
|
|
95
|
+
properties: {
|
|
96
|
+
credentialId: { type: 'string', minLength: 1 }
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
/* ------------------------------------------------------------------ */
|
|
100
|
+
/* Impersonation schemas */
|
|
101
|
+
/* ------------------------------------------------------------------ */
|
|
102
|
+
export const impersonateBodySchema = {
|
|
103
|
+
type: 'object',
|
|
104
|
+
properties: {
|
|
105
|
+
...authIdentifierProperty('userId'),
|
|
106
|
+
login: { type: 'string' },
|
|
107
|
+
...tokenMetadataProperties,
|
|
108
|
+
...keepSessionProperty,
|
|
109
|
+
clientId: { type: 'string' },
|
|
110
|
+
scope: {},
|
|
111
|
+
loginType: { type: 'string' }
|
|
112
|
+
},
|
|
113
|
+
additionalProperties: true
|
|
114
|
+
};
|
|
115
|
+
export const deleteImpersonationQuerySchema = {
|
|
116
|
+
type: 'object',
|
|
117
|
+
additionalProperties: true
|
|
118
|
+
};
|
|
119
|
+
/* ------------------------------------------------------------------ */
|
|
120
|
+
/* OAuth schemas */
|
|
121
|
+
/* ------------------------------------------------------------------ */
|
|
122
|
+
export const oauthProviderParamsSchema = {
|
|
123
|
+
type: 'object',
|
|
124
|
+
required: ['provider'],
|
|
125
|
+
properties: {
|
|
126
|
+
provider: { type: 'string', minLength: 1 }
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
export const oauthStartBodySchema = {
|
|
130
|
+
type: 'object',
|
|
131
|
+
properties: {
|
|
132
|
+
redirectUri: { type: 'string' },
|
|
133
|
+
scope: {},
|
|
134
|
+
state: { type: 'string' },
|
|
135
|
+
extras: { type: 'object' }
|
|
136
|
+
},
|
|
137
|
+
additionalProperties: true
|
|
138
|
+
};
|
|
139
|
+
export const oauthAuthorizeBodySchema = {
|
|
140
|
+
type: 'object',
|
|
141
|
+
required: ['clientId', 'redirectUri'],
|
|
142
|
+
properties: {
|
|
143
|
+
clientId: { type: 'string', minLength: 1 },
|
|
144
|
+
redirectUri: { type: 'string', minLength: 1 },
|
|
145
|
+
scope: {},
|
|
146
|
+
state: { type: 'string' },
|
|
147
|
+
codeChallenge: { type: 'string' },
|
|
148
|
+
codeChallengeMethod: { type: 'string' },
|
|
149
|
+
login: { type: 'string' },
|
|
150
|
+
password: { type: 'string' }
|
|
151
|
+
},
|
|
152
|
+
additionalProperties: false
|
|
153
|
+
};
|
|
154
|
+
export const oauthTokenBodySchema = {
|
|
155
|
+
type: 'object',
|
|
156
|
+
required: ['grant_type'],
|
|
157
|
+
properties: {
|
|
158
|
+
grant_type: { type: 'string', enum: ['authorization_code', 'refresh_token'] },
|
|
159
|
+
code: { type: 'string' },
|
|
160
|
+
redirect_uri: { type: 'string' },
|
|
161
|
+
code_verifier: { type: 'string' },
|
|
162
|
+
client_id: { type: 'string' },
|
|
163
|
+
client_secret: { type: 'string' },
|
|
164
|
+
refresh_token: { type: 'string' },
|
|
165
|
+
scope: { type: 'string' }
|
|
166
|
+
},
|
|
167
|
+
additionalProperties: false
|
|
168
|
+
};
|