@technomoron/api-server-base 1.1.13 → 2.0.0-beta.2

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 (115) hide show
  1. package/dist/cjs/api-server-base.cjs +181 -74
  2. package/dist/cjs/api-server-base.d.ts +66 -29
  3. package/dist/cjs/auth-api/auth-module.d.ts +96 -0
  4. package/dist/cjs/auth-api/auth-module.js +1032 -0
  5. package/dist/cjs/auth-api/compat-auth-storage.d.ts +55 -0
  6. package/dist/cjs/auth-api/compat-auth-storage.js +116 -0
  7. package/dist/cjs/auth-api/mem-auth-store.d.ts +66 -0
  8. package/dist/cjs/auth-api/mem-auth-store.js +135 -0
  9. package/dist/cjs/{auth-module.d.ts → auth-api/module.d.ts} +7 -7
  10. package/dist/cjs/{auth-module.cjs → auth-api/module.js} +1 -1
  11. package/dist/cjs/auth-api/sql-auth-store.d.ts +75 -0
  12. package/dist/cjs/auth-api/sql-auth-store.js +166 -0
  13. package/dist/cjs/auth-api/storage.d.ts +36 -0
  14. package/dist/cjs/{auth-storage.cjs → auth-api/storage.js} +2 -2
  15. package/dist/cjs/auth-api/types.d.ts +29 -0
  16. package/dist/cjs/auth-api/types.js +2 -0
  17. package/dist/cjs/index.cjs +41 -7
  18. package/dist/cjs/index.d.ts +29 -5
  19. package/dist/cjs/oauth/base.d.ts +10 -0
  20. package/dist/cjs/oauth/base.js +6 -0
  21. package/dist/cjs/oauth/memory.d.ts +16 -0
  22. package/dist/cjs/oauth/memory.js +99 -0
  23. package/dist/cjs/oauth/models.d.ts +45 -0
  24. package/dist/cjs/oauth/models.js +58 -0
  25. package/dist/cjs/oauth/sequelize.d.ts +68 -0
  26. package/dist/cjs/oauth/sequelize.js +210 -0
  27. package/dist/cjs/oauth/types.d.ts +50 -0
  28. package/dist/cjs/oauth/types.js +3 -0
  29. package/dist/cjs/passkey/base.d.ts +15 -0
  30. package/dist/cjs/passkey/base.js +6 -0
  31. package/dist/cjs/passkey/memory.d.ts +26 -0
  32. package/dist/cjs/passkey/memory.js +82 -0
  33. package/dist/cjs/passkey/models.d.ts +25 -0
  34. package/dist/cjs/passkey/models.js +115 -0
  35. package/dist/cjs/passkey/sequelize.d.ts +54 -0
  36. package/dist/cjs/passkey/sequelize.js +211 -0
  37. package/dist/cjs/passkey/service.d.ts +17 -0
  38. package/dist/cjs/passkey/service.js +221 -0
  39. package/dist/cjs/passkey/types.d.ts +75 -0
  40. package/dist/cjs/passkey/types.js +2 -0
  41. package/dist/cjs/token/base.d.ts +38 -0
  42. package/dist/cjs/token/base.js +114 -0
  43. package/dist/cjs/token/memory.d.ts +19 -0
  44. package/dist/cjs/token/memory.js +149 -0
  45. package/dist/cjs/token/sequelize.d.ts +58 -0
  46. package/dist/cjs/token/sequelize.js +404 -0
  47. package/dist/cjs/token/types.d.ts +27 -0
  48. package/dist/cjs/token/types.js +2 -0
  49. package/dist/cjs/user/base.d.ts +26 -0
  50. package/dist/cjs/user/base.js +45 -0
  51. package/dist/cjs/user/memory.d.ts +35 -0
  52. package/dist/cjs/user/memory.js +173 -0
  53. package/dist/cjs/user/sequelize.d.ts +41 -0
  54. package/dist/cjs/user/sequelize.js +182 -0
  55. package/dist/cjs/user/types.d.ts +11 -0
  56. package/dist/cjs/user/types.js +2 -0
  57. package/dist/esm/api-server-base.d.ts +66 -29
  58. package/dist/esm/api-server-base.js +179 -72
  59. package/dist/esm/auth-api/auth-module.d.ts +96 -0
  60. package/dist/esm/auth-api/auth-module.js +1030 -0
  61. package/dist/esm/auth-api/compat-auth-storage.d.ts +55 -0
  62. package/dist/esm/auth-api/compat-auth-storage.js +112 -0
  63. package/dist/esm/auth-api/mem-auth-store.d.ts +66 -0
  64. package/dist/esm/auth-api/mem-auth-store.js +131 -0
  65. package/dist/esm/{auth-module.d.ts → auth-api/module.d.ts} +7 -7
  66. package/dist/esm/{auth-module.js → auth-api/module.js} +1 -1
  67. package/dist/esm/auth-api/sql-auth-store.d.ts +75 -0
  68. package/dist/esm/auth-api/sql-auth-store.js +162 -0
  69. package/dist/esm/auth-api/storage.d.ts +36 -0
  70. package/dist/esm/{auth-storage.js → auth-api/storage.js} +2 -2
  71. package/dist/esm/auth-api/types.d.ts +29 -0
  72. package/dist/esm/auth-api/types.js +1 -0
  73. package/dist/esm/index.d.ts +29 -5
  74. package/dist/esm/index.js +19 -2
  75. package/dist/esm/oauth/base.d.ts +10 -0
  76. package/dist/esm/oauth/base.js +2 -0
  77. package/dist/esm/oauth/memory.d.ts +16 -0
  78. package/dist/esm/oauth/memory.js +92 -0
  79. package/dist/esm/oauth/models.d.ts +45 -0
  80. package/dist/esm/oauth/models.js +51 -0
  81. package/dist/esm/oauth/sequelize.d.ts +68 -0
  82. package/dist/esm/oauth/sequelize.js +199 -0
  83. package/dist/esm/oauth/types.d.ts +50 -0
  84. package/dist/esm/oauth/types.js +2 -0
  85. package/dist/esm/passkey/base.d.ts +15 -0
  86. package/dist/esm/passkey/base.js +2 -0
  87. package/dist/esm/passkey/memory.d.ts +26 -0
  88. package/dist/esm/passkey/memory.js +78 -0
  89. package/dist/esm/passkey/models.d.ts +25 -0
  90. package/dist/esm/passkey/models.js +108 -0
  91. package/dist/esm/passkey/sequelize.d.ts +54 -0
  92. package/dist/esm/passkey/sequelize.js +207 -0
  93. package/dist/esm/passkey/service.d.ts +17 -0
  94. package/dist/esm/passkey/service.js +217 -0
  95. package/dist/esm/passkey/types.d.ts +75 -0
  96. package/dist/esm/passkey/types.js +1 -0
  97. package/dist/esm/token/base.d.ts +38 -0
  98. package/dist/esm/token/base.js +107 -0
  99. package/dist/esm/token/memory.d.ts +19 -0
  100. package/dist/esm/token/memory.js +145 -0
  101. package/dist/esm/token/sequelize.d.ts +58 -0
  102. package/dist/esm/token/sequelize.js +400 -0
  103. package/dist/esm/token/types.d.ts +27 -0
  104. package/dist/esm/token/types.js +1 -0
  105. package/dist/esm/user/base.d.ts +26 -0
  106. package/dist/esm/user/base.js +38 -0
  107. package/dist/esm/user/memory.d.ts +35 -0
  108. package/dist/esm/user/memory.js +169 -0
  109. package/dist/esm/user/sequelize.d.ts +41 -0
  110. package/dist/esm/user/sequelize.js +176 -0
  111. package/dist/esm/user/types.d.ts +11 -0
  112. package/dist/esm/user/types.js +1 -0
  113. package/package.json +11 -3
  114. package/dist/cjs/auth-storage.d.ts +0 -133
  115. package/dist/esm/auth-storage.d.ts +0 -133
@@ -0,0 +1,15 @@
1
+ import type { PasskeyChallengeRecord, PasskeyStorageAdapter, PasskeyUserDescriptor, StoredPasskeyCredential } from './types.js';
2
+ import type { AuthIdentifier } from '../auth-api/types.js';
3
+ export declare abstract class PasskeyStore implements PasskeyStorageAdapter {
4
+ abstract resolveUser(params: {
5
+ userId?: AuthIdentifier;
6
+ login?: string;
7
+ }): Promise<PasskeyUserDescriptor | null>;
8
+ abstract listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
9
+ abstract findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
10
+ abstract saveCredential(record: StoredPasskeyCredential): Promise<void>;
11
+ abstract updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
12
+ abstract saveChallenge(record: PasskeyChallengeRecord): Promise<void>;
13
+ abstract consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
14
+ abstract cleanupChallenges(now: Date): Promise<void>;
15
+ }
@@ -0,0 +1,2 @@
1
+ export class PasskeyStore {
2
+ }
@@ -0,0 +1,26 @@
1
+ import { PasskeyStore } from './base.js';
2
+ import type { PasskeyChallengeRecord, PasskeyUserDescriptor, StoredPasskeyCredential } from './types.js';
3
+ import type { AuthIdentifier } from '../auth-api/types.js';
4
+ export interface MemoryPasskeyStoreOptions {
5
+ resolveUser: (params: {
6
+ userId?: AuthIdentifier;
7
+ login?: string;
8
+ }) => Promise<PasskeyUserDescriptor | null>;
9
+ }
10
+ export declare class MemoryPasskeyStore extends PasskeyStore {
11
+ private readonly resolveUserFn;
12
+ private readonly credentials;
13
+ private readonly challenges;
14
+ constructor(options: MemoryPasskeyStoreOptions);
15
+ resolveUser(params: {
16
+ userId?: AuthIdentifier;
17
+ login?: string;
18
+ }): Promise<PasskeyUserDescriptor | null>;
19
+ listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
20
+ findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
21
+ saveCredential(record: StoredPasskeyCredential): Promise<void>;
22
+ updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
23
+ saveChallenge(record: PasskeyChallengeRecord): Promise<void>;
24
+ consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
25
+ cleanupChallenges(now: Date): Promise<void>;
26
+ }
@@ -0,0 +1,78 @@
1
+ import { PasskeyStore } from './base.js';
2
+ function encodeCredentialId(value) {
3
+ return Buffer.isBuffer(value) ? value.toString('base64') : value;
4
+ }
5
+ function normalizeUserId(identifier) {
6
+ if (typeof identifier === 'number' && Number.isFinite(identifier)) {
7
+ return identifier;
8
+ }
9
+ if (typeof identifier === 'string' && /^\d+$/.test(identifier)) {
10
+ return Number(identifier);
11
+ }
12
+ return identifier;
13
+ }
14
+ function cloneCredential(record) {
15
+ return {
16
+ ...record,
17
+ credentialId: Buffer.isBuffer(record.credentialId) ? Buffer.from(record.credentialId) : record.credentialId,
18
+ transports: record.transports ? [...record.transports] : undefined
19
+ };
20
+ }
21
+ export class MemoryPasskeyStore extends PasskeyStore {
22
+ constructor(options) {
23
+ super();
24
+ this.credentials = new Map();
25
+ this.challenges = new Map();
26
+ this.resolveUserFn = options.resolveUser;
27
+ }
28
+ async resolveUser(params) {
29
+ return this.resolveUserFn(params);
30
+ }
31
+ async listUserCredentials(userId) {
32
+ const normalizedUserId = normalizeUserId(userId);
33
+ return [...this.credentials.values()]
34
+ .filter((record) => normalizeUserId(record.userId) === normalizedUserId)
35
+ .map((record) => cloneCredential(record));
36
+ }
37
+ async findCredentialById(credentialId) {
38
+ const record = this.credentials.get(encodeCredentialId(credentialId));
39
+ return record ? cloneCredential(record) : null;
40
+ }
41
+ async saveCredential(record) {
42
+ this.credentials.set(encodeCredentialId(record.credentialId), {
43
+ ...record,
44
+ userId: normalizeUserId(record.userId),
45
+ credentialId: Buffer.isBuffer(record.credentialId) ? Buffer.from(record.credentialId) : record.credentialId,
46
+ transports: record.transports ? [...record.transports] : undefined
47
+ });
48
+ }
49
+ async updateCredentialCounter(credentialId, counter) {
50
+ const key = encodeCredentialId(credentialId);
51
+ const existing = this.credentials.get(key);
52
+ if (existing) {
53
+ existing.counter = counter;
54
+ }
55
+ }
56
+ async saveChallenge(record) {
57
+ this.challenges.set(record.challenge, {
58
+ ...record,
59
+ userId: record.userId !== undefined ? normalizeUserId(record.userId) : undefined,
60
+ metadata: record.metadata ? { ...record.metadata } : {}
61
+ });
62
+ }
63
+ async consumeChallenge(challenge) {
64
+ const record = this.challenges.get(challenge);
65
+ if (!record) {
66
+ return null;
67
+ }
68
+ this.challenges.delete(challenge);
69
+ return { ...record, metadata: record.metadata ? { ...record.metadata } : {} };
70
+ }
71
+ async cleanupChallenges(now) {
72
+ for (const [challenge, record] of this.challenges.entries()) {
73
+ if (record.expiresAt && new Date(record.expiresAt) <= now) {
74
+ this.challenges.delete(challenge);
75
+ }
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,25 @@
1
+ import { Model, type InferAttributes, type InferCreationAttributes, type ModelStatic, type Sequelize } from 'sequelize';
2
+ import type { PasskeyChallengeMetadata } from './service.js';
3
+ export declare class PasskeyCredentialModel extends Model<InferAttributes<PasskeyCredentialModel>, InferCreationAttributes<PasskeyCredentialModel>> {
4
+ credentialId: Buffer;
5
+ userId: number;
6
+ publicKey: Buffer;
7
+ counter: number;
8
+ transports: string[] | null;
9
+ backedUp: boolean;
10
+ deviceType: string;
11
+ createdAt?: Date;
12
+ updatedAt?: Date;
13
+ }
14
+ export declare class PasskeyChallengeModel extends Model<InferAttributes<PasskeyChallengeModel>, InferCreationAttributes<PasskeyChallengeModel>> {
15
+ challenge: string;
16
+ action: 'register' | 'authenticate';
17
+ userId: number | null;
18
+ login: string | null;
19
+ metadata: PasskeyChallengeMetadata | null;
20
+ expiresAt: Date;
21
+ createdAt?: Date;
22
+ updatedAt?: Date;
23
+ }
24
+ export declare function initPasskeyCredentialModel(sequelize: Sequelize): ModelStatic<PasskeyCredentialModel>;
25
+ export declare function initPasskeyChallengeModel(sequelize: Sequelize): ModelStatic<PasskeyChallengeModel>;
@@ -0,0 +1,108 @@
1
+ import { DataTypes, Model } from 'sequelize';
2
+ const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
3
+ function integerIdType(sequelize) {
4
+ return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? DataTypes.INTEGER.UNSIGNED : DataTypes.INTEGER;
5
+ }
6
+ export class PasskeyCredentialModel extends Model {
7
+ }
8
+ export class PasskeyChallengeModel extends Model {
9
+ }
10
+ export function initPasskeyCredentialModel(sequelize) {
11
+ const idType = integerIdType(sequelize);
12
+ return PasskeyCredentialModel.init({
13
+ credentialId: {
14
+ field: 'credential_id',
15
+ type: DataTypes.STRING(768),
16
+ primaryKey: true,
17
+ allowNull: false,
18
+ get() {
19
+ const raw = this.getDataValue('credentialId');
20
+ if (!raw) {
21
+ return raw;
22
+ }
23
+ if (Buffer.isBuffer(raw)) {
24
+ return raw;
25
+ }
26
+ return Buffer.from(raw, 'base64');
27
+ },
28
+ set(value) {
29
+ const encoded = typeof value === 'string' ? value : value.toString('base64');
30
+ this.setDataValue('credentialId', encoded);
31
+ }
32
+ },
33
+ userId: {
34
+ field: 'user_id',
35
+ type: idType,
36
+ allowNull: false
37
+ },
38
+ publicKey: {
39
+ field: 'public_key',
40
+ type: DataTypes.BLOB,
41
+ allowNull: false
42
+ },
43
+ counter: {
44
+ type: DataTypes.INTEGER,
45
+ allowNull: false,
46
+ defaultValue: 0
47
+ },
48
+ transports: {
49
+ type: DataTypes.JSON,
50
+ allowNull: true
51
+ },
52
+ backedUp: {
53
+ field: 'backed_up',
54
+ type: DataTypes.BOOLEAN,
55
+ allowNull: false,
56
+ defaultValue: false
57
+ },
58
+ deviceType: {
59
+ field: 'device_type',
60
+ type: DataTypes.STRING(32),
61
+ allowNull: false,
62
+ defaultValue: 'multiDevice'
63
+ }
64
+ }, {
65
+ sequelize,
66
+ tableName: 'passkey_credentials',
67
+ timestamps: true,
68
+ underscored: true
69
+ });
70
+ }
71
+ export function initPasskeyChallengeModel(sequelize) {
72
+ const idType = integerIdType(sequelize);
73
+ return PasskeyChallengeModel.init({
74
+ challenge: {
75
+ type: DataTypes.STRING(255),
76
+ primaryKey: true,
77
+ allowNull: false
78
+ },
79
+ action: {
80
+ type: DataTypes.STRING(16),
81
+ allowNull: false
82
+ },
83
+ userId: {
84
+ field: 'user_id',
85
+ type: idType,
86
+ allowNull: true
87
+ },
88
+ login: {
89
+ type: DataTypes.STRING(128),
90
+ allowNull: true
91
+ },
92
+ metadata: {
93
+ type: DataTypes.JSON,
94
+ allowNull: true
95
+ },
96
+ expiresAt: {
97
+ field: 'expires_at',
98
+ type: DataTypes.DATE,
99
+ allowNull: false
100
+ }
101
+ }, {
102
+ sequelize,
103
+ tableName: 'passkey_challenges',
104
+ timestamps: true,
105
+ underscored: true,
106
+ indexes: [{ fields: ['expires_at'] }, { fields: ['user_id'] }]
107
+ });
108
+ }
@@ -0,0 +1,54 @@
1
+ import { Model, type InferAttributes, type InferCreationAttributes, type ModelStatic, type Sequelize } from 'sequelize';
2
+ import { PasskeyStore } from './base.js';
3
+ import type { PasskeyChallengeRecord, PasskeyUserDescriptor, StoredPasskeyCredential } from './types.js';
4
+ import type { AuthIdentifier } from '../auth-api/types.js';
5
+ declare class PasskeyCredentialModel extends Model<InferAttributes<PasskeyCredentialModel>, InferCreationAttributes<PasskeyCredentialModel>> {
6
+ credentialId: Buffer;
7
+ userId: number;
8
+ publicKey: Buffer;
9
+ counter: number;
10
+ transports: string[] | null;
11
+ backedUp: boolean;
12
+ deviceType: string;
13
+ createdAt?: Date;
14
+ updatedAt?: Date;
15
+ }
16
+ declare class PasskeyChallengeModel extends Model<InferAttributes<PasskeyChallengeModel>, InferCreationAttributes<PasskeyChallengeModel>> {
17
+ challenge: string;
18
+ action: 'register' | 'authenticate';
19
+ userId: number | null;
20
+ login: string | null;
21
+ metadata: Record<string, unknown> | null;
22
+ expiresAt: Date;
23
+ createdAt?: Date;
24
+ updatedAt?: Date;
25
+ }
26
+ export interface SequelizePasskeyStoreOptions {
27
+ sequelize: Sequelize;
28
+ credentialModel?: ModelStatic<PasskeyCredentialModel>;
29
+ challengeModel?: ModelStatic<PasskeyChallengeModel>;
30
+ credentialModelFactory?: (sequelize: Sequelize) => ModelStatic<PasskeyCredentialModel>;
31
+ challengeModelFactory?: (sequelize: Sequelize) => ModelStatic<PasskeyChallengeModel>;
32
+ resolveUser: (params: {
33
+ userId?: AuthIdentifier;
34
+ login?: string;
35
+ }) => Promise<PasskeyUserDescriptor | null>;
36
+ }
37
+ export declare class SequelizePasskeyStore extends PasskeyStore {
38
+ private readonly resolveUserFn;
39
+ private readonly credentials;
40
+ private readonly challenges;
41
+ constructor(options: SequelizePasskeyStoreOptions);
42
+ resolveUser(params: {
43
+ userId?: AuthIdentifier;
44
+ login?: string;
45
+ }): Promise<PasskeyUserDescriptor | null>;
46
+ listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
47
+ findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
48
+ saveCredential(record: StoredPasskeyCredential): Promise<void>;
49
+ updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
50
+ saveChallenge(record: PasskeyChallengeRecord): Promise<void>;
51
+ consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
52
+ cleanupChallenges(now: Date): Promise<void>;
53
+ }
54
+ export {};
@@ -0,0 +1,207 @@
1
+ import { DataTypes, Model, Op } from 'sequelize';
2
+ import { PasskeyStore } from './base.js';
3
+ const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
4
+ function integerIdType(sequelize) {
5
+ return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? DataTypes.INTEGER.UNSIGNED : DataTypes.INTEGER;
6
+ }
7
+ function encodeCredentialId(value) {
8
+ return Buffer.isBuffer(value) ? value.toString('base64') : value;
9
+ }
10
+ function normalizeUserId(identifier) {
11
+ if (typeof identifier === 'number' && Number.isFinite(identifier)) {
12
+ return identifier;
13
+ }
14
+ if (typeof identifier === 'string' && /^\d+$/.test(identifier)) {
15
+ return Number(identifier);
16
+ }
17
+ throw new Error(`Unable to normalise user identifier: ${identifier}`);
18
+ }
19
+ class PasskeyCredentialModel extends Model {
20
+ }
21
+ class PasskeyChallengeModel extends Model {
22
+ }
23
+ function initPasskeyCredentialModel(sequelize) {
24
+ const idType = integerIdType(sequelize);
25
+ return PasskeyCredentialModel.init({
26
+ credentialId: {
27
+ field: 'credential_id',
28
+ type: DataTypes.STRING(768),
29
+ primaryKey: true,
30
+ allowNull: false,
31
+ get() {
32
+ const raw = this.getDataValue('credentialId');
33
+ if (!raw) {
34
+ return raw;
35
+ }
36
+ if (Buffer.isBuffer(raw)) {
37
+ return raw;
38
+ }
39
+ return Buffer.from(raw, 'base64');
40
+ },
41
+ set(value) {
42
+ const encoded = typeof value === 'string' ? value : value.toString('base64');
43
+ this.setDataValue('credentialId', encoded);
44
+ }
45
+ },
46
+ userId: {
47
+ field: 'user_id',
48
+ type: idType,
49
+ allowNull: false
50
+ },
51
+ publicKey: {
52
+ field: 'public_key',
53
+ type: DataTypes.BLOB,
54
+ allowNull: false
55
+ },
56
+ counter: {
57
+ type: DataTypes.INTEGER,
58
+ allowNull: false,
59
+ defaultValue: 0
60
+ },
61
+ transports: {
62
+ type: DataTypes.JSON,
63
+ allowNull: true
64
+ },
65
+ backedUp: {
66
+ field: 'backed_up',
67
+ type: DataTypes.BOOLEAN,
68
+ allowNull: false,
69
+ defaultValue: false
70
+ },
71
+ deviceType: {
72
+ field: 'device_type',
73
+ type: DataTypes.STRING(32),
74
+ allowNull: false,
75
+ defaultValue: 'multiDevice'
76
+ }
77
+ }, {
78
+ sequelize,
79
+ tableName: 'passkey_credentials',
80
+ timestamps: true,
81
+ underscored: true
82
+ });
83
+ }
84
+ function initPasskeyChallengeModel(sequelize) {
85
+ const idType = integerIdType(sequelize);
86
+ return PasskeyChallengeModel.init({
87
+ challenge: {
88
+ type: DataTypes.STRING(255),
89
+ primaryKey: true,
90
+ allowNull: false
91
+ },
92
+ action: {
93
+ type: DataTypes.STRING(16),
94
+ allowNull: false
95
+ },
96
+ userId: {
97
+ field: 'user_id',
98
+ type: idType,
99
+ allowNull: true
100
+ },
101
+ login: {
102
+ type: DataTypes.STRING(128),
103
+ allowNull: true
104
+ },
105
+ metadata: {
106
+ type: DataTypes.JSON,
107
+ allowNull: true
108
+ },
109
+ expiresAt: {
110
+ field: 'expires_at',
111
+ type: DataTypes.DATE,
112
+ allowNull: false
113
+ }
114
+ }, {
115
+ sequelize,
116
+ tableName: 'passkey_challenges',
117
+ timestamps: true,
118
+ underscored: true,
119
+ indexes: [{ fields: ['expires_at'] }, { fields: ['user_id'] }]
120
+ });
121
+ }
122
+ export class SequelizePasskeyStore extends PasskeyStore {
123
+ constructor(options) {
124
+ super();
125
+ if (!options?.sequelize) {
126
+ throw new Error('SequelizePasskeyStore requires an initialised Sequelize instance');
127
+ }
128
+ this.resolveUserFn = options.resolveUser;
129
+ this.credentials =
130
+ options.credentialModel ??
131
+ (options.credentialModelFactory ?? initPasskeyCredentialModel)(options.sequelize);
132
+ this.challenges =
133
+ options.challengeModel ?? (options.challengeModelFactory ?? initPasskeyChallengeModel)(options.sequelize);
134
+ }
135
+ async resolveUser(params) {
136
+ return this.resolveUserFn(params);
137
+ }
138
+ async listUserCredentials(userId) {
139
+ const models = await this.credentials.findAll({ where: { userId: normalizeUserId(userId) } });
140
+ return models.map((model) => ({
141
+ userId: model.userId,
142
+ credentialId: model.credentialId,
143
+ publicKey: model.publicKey,
144
+ counter: model.counter,
145
+ transports: (model.transports ?? undefined),
146
+ backedUp: model.backedUp,
147
+ deviceType: model.deviceType
148
+ }));
149
+ }
150
+ async findCredentialById(credentialId) {
151
+ const model = await this.credentials.findByPk(encodeCredentialId(credentialId));
152
+ if (!model) {
153
+ return null;
154
+ }
155
+ return {
156
+ userId: model.userId,
157
+ credentialId: model.credentialId,
158
+ publicKey: model.publicKey,
159
+ counter: model.counter,
160
+ transports: (model.transports ?? undefined),
161
+ backedUp: model.backedUp,
162
+ deviceType: model.deviceType
163
+ };
164
+ }
165
+ async saveCredential(record) {
166
+ await this.credentials.upsert({
167
+ credentialId: record.credentialId,
168
+ userId: normalizeUserId(record.userId),
169
+ publicKey: record.publicKey,
170
+ counter: record.counter,
171
+ transports: record.transports ?? null,
172
+ backedUp: record.backedUp,
173
+ deviceType: record.deviceType
174
+ });
175
+ }
176
+ async updateCredentialCounter(credentialId, counter) {
177
+ await this.credentials.update({ counter }, { where: { credentialId: encodeCredentialId(credentialId) } });
178
+ }
179
+ async saveChallenge(record) {
180
+ await this.challenges.upsert({
181
+ challenge: record.challenge,
182
+ action: record.action,
183
+ userId: record.userId !== undefined ? normalizeUserId(record.userId) : null,
184
+ login: record.login ?? null,
185
+ metadata: (record.metadata ?? {}),
186
+ expiresAt: record.expiresAt
187
+ });
188
+ }
189
+ async consumeChallenge(challenge) {
190
+ const model = await this.challenges.findByPk(challenge);
191
+ if (!model) {
192
+ return null;
193
+ }
194
+ await model.destroy();
195
+ return {
196
+ challenge: model.challenge,
197
+ action: model.action,
198
+ userId: model.userId ?? undefined,
199
+ login: model.login ?? undefined,
200
+ expiresAt: model.expiresAt,
201
+ metadata: model.metadata ?? {}
202
+ };
203
+ }
204
+ async cleanupChallenges(now) {
205
+ await this.challenges.destroy({ where: { expiresAt: { [Op.lte]: now } } });
206
+ }
207
+ }
@@ -0,0 +1,17 @@
1
+ import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyStorageAdapter, PasskeyVerificationParams, PasskeyVerificationResult, PasskeyServiceConfig } from './types.js';
2
+ export type { CredentialDeviceType, PasskeyChallenge, PasskeyChallengeParams, PasskeyChallengeRecord, PasskeyChallengeMetadata, PasskeyUserDescriptor, PasskeyVerificationParams, PasskeyVerificationResult, PasskeyServiceConfig, PasskeyStorageAdapter } from './types.js';
3
+ type Logger = Pick<typeof console, 'error' | 'warn'>;
4
+ export declare class PasskeyService {
5
+ private readonly config;
6
+ private readonly adapter;
7
+ private readonly logger;
8
+ constructor(config: PasskeyServiceConfig, adapter: PasskeyStorageAdapter, logger?: Logger);
9
+ createChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
10
+ verifyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
11
+ private createRegistrationChallenge;
12
+ private createAuthenticationChallenge;
13
+ private verifyRegistration;
14
+ private verifyAuthentication;
15
+ private requireUser;
16
+ private createExpiry;
17
+ }