@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,87 @@
|
|
|
1
|
+
import { Sequelize, type SyncOptions } from 'sequelize';
|
|
2
|
+
import { SequelizeOAuthStore, type SequelizeOAuthStoreOptions } from '../oauth/sequelize.js';
|
|
3
|
+
import { SequelizePasskeyStore } from '../passkey/sequelize.js';
|
|
4
|
+
import { type SequelizeTokenStoreOptions } from '../token/sequelize.js';
|
|
5
|
+
import { SequelizeUserStore, type AuthUserAttributes, GenericUserModel, GenericUserModelStatic } from '../user/sequelize.js';
|
|
6
|
+
import type { AuthAdapter, AuthIdentifier } from './types.js';
|
|
7
|
+
import type { AuthCodeData, AuthCodeRequest, OAuthClient } from '../oauth/types.js';
|
|
8
|
+
import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyServiceConfig, PasskeyUserDescriptor, StoredPasskeyCredential, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
|
|
9
|
+
import type { TokenStore } from '../token/base.js';
|
|
10
|
+
import type { Token } from '../token/types.js';
|
|
11
|
+
interface PasskeyOptions extends Partial<PasskeyServiceConfig> {
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface SqlAuthStoreTablePrefixes {
|
|
15
|
+
user?: string;
|
|
16
|
+
token?: string;
|
|
17
|
+
passkey?: string;
|
|
18
|
+
oauth?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface SqlAuthStoreParams<UserAttributes extends AuthUserAttributes = AuthUserAttributes, PublicUserShape extends Omit<UserAttributes, 'password'> = Omit<UserAttributes, 'password'>> {
|
|
21
|
+
sequelize: Sequelize;
|
|
22
|
+
syncOptions?: SyncOptions;
|
|
23
|
+
bcryptRounds?: number;
|
|
24
|
+
passwordPepper?: string;
|
|
25
|
+
tablePrefix?: string;
|
|
26
|
+
tablePrefixes?: SqlAuthStoreTablePrefixes;
|
|
27
|
+
userModel?: GenericUserModelStatic;
|
|
28
|
+
userModelFactory?: (sequelize: Sequelize, options?: {
|
|
29
|
+
tablePrefix?: string;
|
|
30
|
+
}) => GenericUserModelStatic;
|
|
31
|
+
userRecordMapper?: (model: GenericUserModel) => UserAttributes;
|
|
32
|
+
publicUserMapper?: (user: UserAttributes) => PublicUserShape;
|
|
33
|
+
passkeyUserMapper?: (user: UserAttributes) => PasskeyUserDescriptor;
|
|
34
|
+
passkeys?: false | PasskeyOptions;
|
|
35
|
+
canImpersonate?: (params: {
|
|
36
|
+
realUserId: AuthIdentifier;
|
|
37
|
+
effectiveUserId: AuthIdentifier;
|
|
38
|
+
}) => boolean | Promise<boolean>;
|
|
39
|
+
tokenStore?: TokenStore;
|
|
40
|
+
tokenStoreOptions?: Omit<SequelizeTokenStoreOptions, 'sequelize'>;
|
|
41
|
+
oauthStoreOptions?: Omit<SequelizeOAuthStoreOptions, 'sequelize'>;
|
|
42
|
+
}
|
|
43
|
+
export declare class SqlAuthStore<UserAttributes extends AuthUserAttributes = AuthUserAttributes, PublicUserShape extends Omit<UserAttributes, 'password'> = Omit<UserAttributes, 'password'>> implements AuthAdapter<UserAttributes, PublicUserShape> {
|
|
44
|
+
readonly userStore: SequelizeUserStore<UserAttributes, PublicUserShape>;
|
|
45
|
+
readonly tokenStore: TokenStore;
|
|
46
|
+
readonly passkeyStore?: SequelizePasskeyStore;
|
|
47
|
+
readonly oauthStore: SequelizeOAuthStore;
|
|
48
|
+
private readonly adapter;
|
|
49
|
+
private readonly sequelize;
|
|
50
|
+
private closed;
|
|
51
|
+
private readonly syncOptions?;
|
|
52
|
+
constructor(params: SqlAuthStoreParams<UserAttributes, PublicUserShape>);
|
|
53
|
+
initialise(withSync?: boolean): Promise<void>;
|
|
54
|
+
close(): Promise<void>;
|
|
55
|
+
getUser(identifier: AuthIdentifier): Promise<UserAttributes | null>;
|
|
56
|
+
getUserPasswordHash(user: UserAttributes): string;
|
|
57
|
+
getUserId(user: UserAttributes): AuthIdentifier;
|
|
58
|
+
filterUser(user: UserAttributes): PublicUserShape;
|
|
59
|
+
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
60
|
+
storeToken(data: Token): Promise<void>;
|
|
61
|
+
getToken(query: Partial<Token> & {
|
|
62
|
+
userId?: AuthIdentifier;
|
|
63
|
+
ruid?: AuthIdentifier;
|
|
64
|
+
}, opts?: {
|
|
65
|
+
includeExpired?: boolean;
|
|
66
|
+
}): Promise<Token | null>;
|
|
67
|
+
deleteToken(query: Partial<Token> & {
|
|
68
|
+
userId?: AuthIdentifier;
|
|
69
|
+
ruid?: AuthIdentifier;
|
|
70
|
+
}): Promise<number>;
|
|
71
|
+
updateToken(updates: Partial<Token> & {
|
|
72
|
+
refreshToken: string;
|
|
73
|
+
}): Promise<boolean>;
|
|
74
|
+
createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
|
|
75
|
+
verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
|
|
76
|
+
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
77
|
+
deletePasskeyCredential(credentialId: Buffer | string): Promise<boolean>;
|
|
78
|
+
getClient(clientId: string): Promise<OAuthClient | null>;
|
|
79
|
+
verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
|
|
80
|
+
createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
|
|
81
|
+
consumeAuthCode(code: string, clientId: string): Promise<AuthCodeData | null>;
|
|
82
|
+
canImpersonate(params: {
|
|
83
|
+
realUserId: AuthIdentifier;
|
|
84
|
+
effectiveUserId: AuthIdentifier;
|
|
85
|
+
}): Promise<boolean>;
|
|
86
|
+
}
|
|
87
|
+
export {};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { SequelizeOAuthStore } from '../oauth/sequelize.js';
|
|
2
|
+
import { normalizePasskeyConfig } from '../passkey/config.js';
|
|
3
|
+
import { SequelizePasskeyStore } from '../passkey/sequelize.js';
|
|
4
|
+
import { normalizeTablePrefix } from '../sequelize-utils.js';
|
|
5
|
+
import { SequelizeTokenStore } from '../token/sequelize.js';
|
|
6
|
+
import { SequelizeUserStore } from '../user/sequelize.js';
|
|
7
|
+
import { CompositeAuthAdapter } from './compat-auth-storage.js';
|
|
8
|
+
import { toOptionalStringId } from './user-id.js';
|
|
9
|
+
function resolveTablePrefix(...prefixes) {
|
|
10
|
+
for (const prefix of prefixes) {
|
|
11
|
+
const normalized = normalizeTablePrefix(prefix);
|
|
12
|
+
if (normalized) {
|
|
13
|
+
return normalized;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
export class SqlAuthStore {
|
|
19
|
+
constructor(params) {
|
|
20
|
+
this.closed = false;
|
|
21
|
+
if (!params?.sequelize) {
|
|
22
|
+
throw new Error('SqlAuthStore requires an initialised Sequelize instance');
|
|
23
|
+
}
|
|
24
|
+
this.sequelize = params.sequelize;
|
|
25
|
+
this.syncOptions = params.syncOptions;
|
|
26
|
+
const moduleTablePrefixes = params.tablePrefixes ?? {};
|
|
27
|
+
const userTablePrefix = resolveTablePrefix(moduleTablePrefixes.user, params.tablePrefix);
|
|
28
|
+
this.userStore = new SequelizeUserStore({
|
|
29
|
+
sequelize: this.sequelize,
|
|
30
|
+
userModel: params.userModel,
|
|
31
|
+
userModelFactory: params.userModelFactory,
|
|
32
|
+
recordMapper: params.userRecordMapper,
|
|
33
|
+
toPublic: params.publicUserMapper,
|
|
34
|
+
bcryptRounds: params.bcryptRounds,
|
|
35
|
+
bcryptPepper: params.passwordPepper,
|
|
36
|
+
tablePrefix: userTablePrefix
|
|
37
|
+
});
|
|
38
|
+
const tokenTablePrefix = resolveTablePrefix(params.tokenStoreOptions?.tablePrefix, moduleTablePrefixes.token, params.tablePrefix);
|
|
39
|
+
this.tokenStore =
|
|
40
|
+
params.tokenStore ??
|
|
41
|
+
new SequelizeTokenStore({
|
|
42
|
+
sequelize: this.sequelize,
|
|
43
|
+
...params.tokenStoreOptions,
|
|
44
|
+
tablePrefix: tokenTablePrefix
|
|
45
|
+
});
|
|
46
|
+
const oauthTablePrefix = resolveTablePrefix(params.oauthStoreOptions?.tablePrefix, moduleTablePrefixes.oauth, params.tablePrefix);
|
|
47
|
+
this.oauthStore = new SequelizeOAuthStore({
|
|
48
|
+
sequelize: this.sequelize,
|
|
49
|
+
...params.oauthStoreOptions,
|
|
50
|
+
tablePrefix: oauthTablePrefix,
|
|
51
|
+
bcryptRounds: params.bcryptRounds
|
|
52
|
+
});
|
|
53
|
+
let passkeyStore;
|
|
54
|
+
let passkeyConfig;
|
|
55
|
+
if (params.passkeys !== false) {
|
|
56
|
+
const passkeyTablePrefix = resolveTablePrefix(moduleTablePrefixes.passkey, params.tablePrefix);
|
|
57
|
+
passkeyConfig = normalizePasskeyConfig(params.passkeys ?? {});
|
|
58
|
+
const resolveUser = async (lookup) => {
|
|
59
|
+
const found = await this.userStore.findUser(lookup.userId ?? lookup.login ?? '');
|
|
60
|
+
if (!found) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const mapper = params.passkeyUserMapper ??
|
|
64
|
+
((user) => ({
|
|
65
|
+
id: (this.userStore.getUserId(user) ?? user['user_id']),
|
|
66
|
+
login: user.login ?? String(this.userStore.getUserId(user)),
|
|
67
|
+
displayName: user.login ?? String(this.userStore.getUserId(user))
|
|
68
|
+
}));
|
|
69
|
+
return mapper(found);
|
|
70
|
+
};
|
|
71
|
+
passkeyStore = new SequelizePasskeyStore({
|
|
72
|
+
sequelize: this.sequelize,
|
|
73
|
+
resolveUser,
|
|
74
|
+
tablePrefix: passkeyTablePrefix
|
|
75
|
+
});
|
|
76
|
+
this.passkeyStore = passkeyStore;
|
|
77
|
+
}
|
|
78
|
+
this.adapter = new CompositeAuthAdapter({
|
|
79
|
+
userStore: this.userStore,
|
|
80
|
+
tokenStore: this.tokenStore,
|
|
81
|
+
passkeys: passkeyStore && passkeyConfig ? { store: passkeyStore, config: passkeyConfig } : undefined,
|
|
82
|
+
oauthStore: this.oauthStore,
|
|
83
|
+
canImpersonate: params.canImpersonate
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async initialise(withSync = false) {
|
|
87
|
+
await this.sequelize.authenticate();
|
|
88
|
+
if (withSync) {
|
|
89
|
+
await this.sequelize.sync(this.syncOptions);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async close() {
|
|
93
|
+
if (this.closed) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.closed = true;
|
|
97
|
+
try {
|
|
98
|
+
await this.sequelize.close();
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
const message = error?.message ?? '';
|
|
102
|
+
if (!/closed/i.test(message)) {
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
// Prevent double-close errors when the same Sequelize instance is shared with other code.
|
|
108
|
+
this.sequelize.close = async () => { };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async getUser(identifier) {
|
|
112
|
+
return this.adapter.getUser(identifier);
|
|
113
|
+
}
|
|
114
|
+
getUserPasswordHash(user) {
|
|
115
|
+
return this.adapter.getUserPasswordHash(user);
|
|
116
|
+
}
|
|
117
|
+
getUserId(user) {
|
|
118
|
+
return this.adapter.getUserId(user);
|
|
119
|
+
}
|
|
120
|
+
filterUser(user) {
|
|
121
|
+
return this.adapter.filterUser(user);
|
|
122
|
+
}
|
|
123
|
+
async verifyPassword(password, hash) {
|
|
124
|
+
return this.adapter.verifyPassword(password, hash);
|
|
125
|
+
}
|
|
126
|
+
async storeToken(data) {
|
|
127
|
+
return this.adapter.storeToken(data);
|
|
128
|
+
}
|
|
129
|
+
async getToken(query, opts) {
|
|
130
|
+
const normalized = {
|
|
131
|
+
...query,
|
|
132
|
+
userId: toOptionalStringId(query.userId),
|
|
133
|
+
ruid: toOptionalStringId(query.ruid)
|
|
134
|
+
};
|
|
135
|
+
return this.adapter.getToken(normalized, opts);
|
|
136
|
+
}
|
|
137
|
+
async deleteToken(query) {
|
|
138
|
+
const normalized = {
|
|
139
|
+
...query,
|
|
140
|
+
userId: toOptionalStringId(query.userId),
|
|
141
|
+
ruid: toOptionalStringId(query.ruid)
|
|
142
|
+
};
|
|
143
|
+
return this.adapter.deleteToken(normalized);
|
|
144
|
+
}
|
|
145
|
+
async updateToken(updates) {
|
|
146
|
+
return this.adapter.updateToken(updates);
|
|
147
|
+
}
|
|
148
|
+
async createPasskeyChallenge(params) {
|
|
149
|
+
return this.adapter.createPasskeyChallenge(params);
|
|
150
|
+
}
|
|
151
|
+
async verifyPasskeyResponse(params) {
|
|
152
|
+
return this.adapter.verifyPasskeyResponse(params);
|
|
153
|
+
}
|
|
154
|
+
async listUserCredentials(userId) {
|
|
155
|
+
return this.adapter.listUserCredentials(userId);
|
|
156
|
+
}
|
|
157
|
+
async deletePasskeyCredential(credentialId) {
|
|
158
|
+
return this.adapter.deletePasskeyCredential(credentialId);
|
|
159
|
+
}
|
|
160
|
+
async getClient(clientId) {
|
|
161
|
+
return this.adapter.getClient(clientId);
|
|
162
|
+
}
|
|
163
|
+
async verifyClientSecret(client, clientSecret) {
|
|
164
|
+
return this.adapter.verifyClientSecret(client, clientSecret);
|
|
165
|
+
}
|
|
166
|
+
async createAuthCode(request) {
|
|
167
|
+
return this.adapter.createAuthCode(request);
|
|
168
|
+
}
|
|
169
|
+
async consumeAuthCode(code, clientId) {
|
|
170
|
+
return this.adapter.consumeAuthCode(code, clientId);
|
|
171
|
+
}
|
|
172
|
+
async canImpersonate(params) {
|
|
173
|
+
return this.adapter.canImpersonate(params);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AuthAdapter, AuthIdentifier } from './types.js';
|
|
2
|
+
import type { AuthCodeData, AuthCodeRequest, OAuthClient } from '../oauth/types.js';
|
|
3
|
+
import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyVerificationParams, PasskeyVerificationResult, StoredPasskeyCredential } from '../passkey/types.js';
|
|
4
|
+
import type { Token } from '../token/types.js';
|
|
5
|
+
export declare class BaseAuthAdapter<UserRow = unknown, SafeUser = unknown> implements AuthAdapter<UserRow, SafeUser> {
|
|
6
|
+
getUser(identifier: AuthIdentifier): Promise<UserRow | null>;
|
|
7
|
+
getUserPasswordHash(user: UserRow): string;
|
|
8
|
+
getUserId(user: UserRow): AuthIdentifier;
|
|
9
|
+
filterUser(user: UserRow): SafeUser;
|
|
10
|
+
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
11
|
+
storeToken(data: Token): Promise<void>;
|
|
12
|
+
getToken(query: Partial<Omit<Token, 'userId' | 'ruid'>> & {
|
|
13
|
+
userId?: string | number;
|
|
14
|
+
ruid?: string | number;
|
|
15
|
+
}, opts?: {
|
|
16
|
+
includeExpired?: boolean;
|
|
17
|
+
}): Promise<Token | null>;
|
|
18
|
+
deleteToken(query: Partial<Omit<Token, 'userId' | 'ruid'>> & {
|
|
19
|
+
userId?: string | number;
|
|
20
|
+
ruid?: string | number;
|
|
21
|
+
}): Promise<number>;
|
|
22
|
+
updateToken(updates: Partial<Token> & {
|
|
23
|
+
refreshToken: string;
|
|
24
|
+
}): Promise<boolean>;
|
|
25
|
+
createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
|
|
26
|
+
verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
|
|
27
|
+
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
28
|
+
deletePasskeyCredential(credentialId: Buffer | string): Promise<boolean>;
|
|
29
|
+
getClient(clientId: string): Promise<OAuthClient | null>;
|
|
30
|
+
verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
|
|
31
|
+
createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
|
|
32
|
+
consumeAuthCode(code: string, clientId: string): Promise<AuthCodeData | null>;
|
|
33
|
+
canImpersonate(params: {
|
|
34
|
+
realUserId: AuthIdentifier;
|
|
35
|
+
effectiveUserId: AuthIdentifier;
|
|
36
|
+
}): Promise<boolean>;
|
|
37
|
+
}
|
|
38
|
+
export declare const nullAuthAdapter: AuthAdapter<unknown, unknown>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Handy base you can extend when wiring a real auth adapter. Every method
|
|
2
|
+
// throws by default so unimplemented hooks fail loudly.
|
|
3
|
+
export class BaseAuthAdapter {
|
|
4
|
+
// Override to load a user record by identifier
|
|
5
|
+
async getUser(identifier) {
|
|
6
|
+
void identifier;
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
// Override to return the stored password hash for the user
|
|
10
|
+
getUserPasswordHash(user) {
|
|
11
|
+
void user;
|
|
12
|
+
throw new Error('Auth storage not configured');
|
|
13
|
+
}
|
|
14
|
+
// Override to expose the canonical user identifier
|
|
15
|
+
getUserId(user) {
|
|
16
|
+
void user;
|
|
17
|
+
throw new Error('Auth storage not configured');
|
|
18
|
+
}
|
|
19
|
+
// Override to strip sensitive fields from the user record
|
|
20
|
+
filterUser(user) {
|
|
21
|
+
return user;
|
|
22
|
+
}
|
|
23
|
+
// Override to validate a raw password against the stored hash
|
|
24
|
+
async verifyPassword(password, hash) {
|
|
25
|
+
void password;
|
|
26
|
+
void hash;
|
|
27
|
+
throw new Error('Auth storage not configured');
|
|
28
|
+
}
|
|
29
|
+
// Override to persist newly issued tokens
|
|
30
|
+
async storeToken(data) {
|
|
31
|
+
void data;
|
|
32
|
+
throw new Error('Auth storage not configured');
|
|
33
|
+
}
|
|
34
|
+
// Override to look up a stored token by query
|
|
35
|
+
async getToken(query, opts) {
|
|
36
|
+
void query;
|
|
37
|
+
void opts;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
// Override to remove stored tokens that match the query
|
|
41
|
+
async deleteToken(query) {
|
|
42
|
+
void query;
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
// Override to update metadata for an existing refresh token
|
|
46
|
+
async updateToken(updates) {
|
|
47
|
+
void updates;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
// Override to create a new passkey challenge record
|
|
51
|
+
async createPasskeyChallenge(params) {
|
|
52
|
+
void params;
|
|
53
|
+
throw new Error('Auth storage not configured');
|
|
54
|
+
}
|
|
55
|
+
// Override to verify an incoming WebAuthn response
|
|
56
|
+
async verifyPasskeyResponse(params) {
|
|
57
|
+
void params;
|
|
58
|
+
throw new Error('Auth storage not configured');
|
|
59
|
+
}
|
|
60
|
+
// Override to list passkey credentials for a user
|
|
61
|
+
async listUserCredentials(userId) {
|
|
62
|
+
void userId;
|
|
63
|
+
throw new Error('Auth storage not configured');
|
|
64
|
+
}
|
|
65
|
+
// Override to delete a passkey credential
|
|
66
|
+
async deletePasskeyCredential(credentialId) {
|
|
67
|
+
void credentialId;
|
|
68
|
+
throw new Error('Auth storage not configured');
|
|
69
|
+
}
|
|
70
|
+
// Override to fetch an OAuth client by identifier
|
|
71
|
+
async getClient(clientId) {
|
|
72
|
+
void clientId;
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
// Override to compare a provided client secret against storage
|
|
76
|
+
async verifyClientSecret(client, clientSecret) {
|
|
77
|
+
void client;
|
|
78
|
+
void clientSecret;
|
|
79
|
+
throw new Error('Auth storage not configured');
|
|
80
|
+
}
|
|
81
|
+
// Override to create a new authorization code entry
|
|
82
|
+
async createAuthCode(request) {
|
|
83
|
+
void request;
|
|
84
|
+
throw new Error('Auth storage not configured');
|
|
85
|
+
}
|
|
86
|
+
// Override to consume and invalidate an authorization code
|
|
87
|
+
async consumeAuthCode(code, clientId) {
|
|
88
|
+
void code;
|
|
89
|
+
void clientId;
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
// Override to decide if a real user may impersonate another user
|
|
93
|
+
async canImpersonate(params) {
|
|
94
|
+
void params;
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export const nullAuthAdapter = new BaseAuthAdapter();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AuthCodeData, AuthCodeRequest, OAuthClient } from '../oauth/types.js';
|
|
2
|
+
import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyVerificationParams, PasskeyVerificationResult, StoredPasskeyCredential } from '../passkey/types.js';
|
|
3
|
+
import type { Token } from '../token/types.js';
|
|
4
|
+
export type AuthIdentifier = string | number;
|
|
5
|
+
/** @internal */
|
|
6
|
+
export interface AuthAdapter<UserRow, SafeUser> {
|
|
7
|
+
getUser(identifier: AuthIdentifier): Promise<UserRow | null>;
|
|
8
|
+
getUserPasswordHash(user: UserRow): string;
|
|
9
|
+
getUserId(user: UserRow): AuthIdentifier;
|
|
10
|
+
filterUser(user: UserRow): SafeUser;
|
|
11
|
+
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
12
|
+
storeToken(data: Token): Promise<void>;
|
|
13
|
+
getToken(query: Partial<Token>, opts?: {
|
|
14
|
+
includeExpired?: boolean;
|
|
15
|
+
}): Promise<Token | null>;
|
|
16
|
+
deleteToken(query: Partial<Token>): Promise<number>;
|
|
17
|
+
updateToken?(updates: Partial<Token> & {
|
|
18
|
+
refreshToken: string;
|
|
19
|
+
}): Promise<boolean>;
|
|
20
|
+
createPasskeyChallenge?(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
|
|
21
|
+
verifyPasskeyResponse?(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
|
|
22
|
+
listUserCredentials?(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
23
|
+
deletePasskeyCredential?(credentialId: Buffer | string): Promise<boolean>;
|
|
24
|
+
getClient?(clientId: string): Promise<OAuthClient | null>;
|
|
25
|
+
verifyClientSecret?(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
|
|
26
|
+
createAuthCode?(request: AuthCodeRequest): Promise<AuthCodeData>;
|
|
27
|
+
consumeAuthCode?(code: string, clientId: string): Promise<AuthCodeData | null>;
|
|
28
|
+
canImpersonate?(params: {
|
|
29
|
+
realUserId: AuthIdentifier;
|
|
30
|
+
effectiveUserId: AuthIdentifier;
|
|
31
|
+
}): Promise<boolean>;
|
|
32
|
+
}
|
|
33
|
+
/** @internal */
|
|
34
|
+
export type AuthStorage<UserRow, SafeUser> = AuthAdapter<UserRow, SafeUser>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AuthIdentifier } from './types.js';
|
|
2
|
+
export declare function normalizeComparableUserId(identifier: AuthIdentifier): string;
|
|
3
|
+
export declare function normalizeNumericUserId(identifier: AuthIdentifier): number;
|
|
4
|
+
export declare function normalizeStringUserId(identifier: AuthIdentifier): string;
|
|
5
|
+
export declare function toOptionalStringId(value: AuthIdentifier | undefined | null): string | undefined;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export function normalizeComparableUserId(identifier) {
|
|
2
|
+
if (typeof identifier === 'number' && Number.isFinite(identifier)) {
|
|
3
|
+
return String(identifier);
|
|
4
|
+
}
|
|
5
|
+
if (typeof identifier === 'string') {
|
|
6
|
+
const trimmed = identifier.trim();
|
|
7
|
+
if (trimmed.length === 0) {
|
|
8
|
+
throw new Error(`Unable to normalise user identifier: ${identifier}`);
|
|
9
|
+
}
|
|
10
|
+
if (/^\d+$/.test(trimmed)) {
|
|
11
|
+
const num = Number(trimmed);
|
|
12
|
+
// Avoid precision loss for large numeric strings
|
|
13
|
+
if (!Number.isSafeInteger(num)) {
|
|
14
|
+
return trimmed;
|
|
15
|
+
}
|
|
16
|
+
return String(num);
|
|
17
|
+
}
|
|
18
|
+
return trimmed;
|
|
19
|
+
}
|
|
20
|
+
throw new Error(`Unable to normalise user identifier: ${identifier}`);
|
|
21
|
+
}
|
|
22
|
+
export function normalizeNumericUserId(identifier) {
|
|
23
|
+
const normalized = normalizeComparableUserId(identifier);
|
|
24
|
+
if (/^\d+$/.test(normalized)) {
|
|
25
|
+
const num = Number(normalized);
|
|
26
|
+
if (!Number.isSafeInteger(num)) {
|
|
27
|
+
throw new Error(`Sequelize OAuth/Passkey store requires numeric user IDs within safe integer range: ${identifier}`);
|
|
28
|
+
}
|
|
29
|
+
return num;
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`Sequelize OAuth/Passkey store requires numeric user IDs: ${identifier}`);
|
|
32
|
+
}
|
|
33
|
+
export function normalizeStringUserId(identifier) {
|
|
34
|
+
return normalizeComparableUserId(identifier);
|
|
35
|
+
}
|
|
36
|
+
export function toOptionalStringId(value) {
|
|
37
|
+
if (value === undefined || value === null) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return String(value);
|
|
41
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ApiCookieOptions } from './apicore-server.js';
|
|
2
|
+
export interface AuthCookieConfig {
|
|
3
|
+
cookieSecure?: boolean | 'auto';
|
|
4
|
+
cookieSameSite?: 'lax' | 'strict' | 'none';
|
|
5
|
+
cookieHttpOnly?: boolean;
|
|
6
|
+
cookieDomain?: string;
|
|
7
|
+
cookiePath?: string;
|
|
8
|
+
devMode?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function buildAuthCookieOptions(config: AuthCookieConfig, req: {
|
|
11
|
+
headers: Record<string, string | string[] | undefined>;
|
|
12
|
+
protocol?: string;
|
|
13
|
+
}): ApiCookieOptions;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
function firstHeaderValue(value) {
|
|
2
|
+
if (typeof value === 'string') {
|
|
3
|
+
return value;
|
|
4
|
+
}
|
|
5
|
+
if (Array.isArray(value)) {
|
|
6
|
+
return value[0] ?? '';
|
|
7
|
+
}
|
|
8
|
+
return '';
|
|
9
|
+
}
|
|
10
|
+
function resolveOriginHostname(origin) {
|
|
11
|
+
try {
|
|
12
|
+
const url = new URL(origin);
|
|
13
|
+
const hostname = url.hostname.trim().toLowerCase();
|
|
14
|
+
return hostname.length > 0 ? hostname : null;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function isLocalhostOrigin(origin) {
|
|
21
|
+
const hostname = resolveOriginHostname(origin);
|
|
22
|
+
if (!hostname) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return hostname === 'localhost' || hostname.endsWith('.localhost');
|
|
26
|
+
}
|
|
27
|
+
export function buildAuthCookieOptions(config, req) {
|
|
28
|
+
const forwardedProto = firstHeaderValue(req.headers['x-forwarded-proto']).split(',')[0].trim().toLowerCase();
|
|
29
|
+
const isHttps = forwardedProto === 'https' || req.protocol === 'https';
|
|
30
|
+
const origin = firstHeaderValue(req.headers.origin ?? req.headers.referer);
|
|
31
|
+
let secure;
|
|
32
|
+
if (config.cookieSecure === true) {
|
|
33
|
+
secure = true;
|
|
34
|
+
}
|
|
35
|
+
else if (config.cookieSecure === false) {
|
|
36
|
+
secure = false;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
secure = isHttps;
|
|
40
|
+
}
|
|
41
|
+
let sameSite = config.cookieSameSite ?? 'lax';
|
|
42
|
+
if (sameSite !== 'lax' && sameSite !== 'strict' && sameSite !== 'none') {
|
|
43
|
+
sameSite = 'lax';
|
|
44
|
+
}
|
|
45
|
+
let resolvedSecure = secure;
|
|
46
|
+
if (sameSite === 'none' && resolvedSecure !== true) {
|
|
47
|
+
// Modern browsers reject SameSite=None cookies unless Secure is set.
|
|
48
|
+
resolvedSecure = true;
|
|
49
|
+
}
|
|
50
|
+
const options = {
|
|
51
|
+
httpOnly: config.cookieHttpOnly ?? true,
|
|
52
|
+
secure: resolvedSecure,
|
|
53
|
+
sameSite,
|
|
54
|
+
domain: config.cookieDomain || undefined,
|
|
55
|
+
path: config.cookiePath || '/',
|
|
56
|
+
maxAge: undefined
|
|
57
|
+
};
|
|
58
|
+
if (config.devMode && isLocalhostOrigin(origin)) {
|
|
59
|
+
// Domain cookies do not work on localhost; avoid breaking local development when cookieDomain is set.
|
|
60
|
+
options.domain = undefined;
|
|
61
|
+
}
|
|
62
|
+
return options;
|
|
63
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
interface ClientInfoRequest {
|
|
2
|
+
headers: Record<string, string | string[] | undefined>;
|
|
3
|
+
ips?: string[];
|
|
4
|
+
ip?: string;
|
|
5
|
+
socket?: {
|
|
6
|
+
remoteAddress?: string;
|
|
7
|
+
};
|
|
8
|
+
connection?: {
|
|
9
|
+
remoteAddress?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface ClientAgentProfile {
|
|
13
|
+
ua: string;
|
|
14
|
+
browser: string;
|
|
15
|
+
os: string;
|
|
16
|
+
device: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ClientInfo extends ClientAgentProfile {
|
|
19
|
+
ip: string | null;
|
|
20
|
+
ipchain: string[];
|
|
21
|
+
}
|
|
22
|
+
interface ApiRequestWithClientInfo {
|
|
23
|
+
req: ClientInfoRequest;
|
|
24
|
+
clientInfo?: ClientInfo;
|
|
25
|
+
}
|
|
26
|
+
export declare function ensureClientInfo(apiReq: ApiRequestWithClientInfo, trustProxy?: boolean | number): ClientInfo;
|
|
27
|
+
export {};
|