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

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/README.txt +81 -28
  2. package/dist/cjs/api-module.cjs +9 -0
  3. package/dist/cjs/api-module.d.ts +7 -4
  4. package/dist/cjs/api-server-base.cjs +607 -99
  5. package/dist/cjs/api-server-base.d.ts +80 -23
  6. package/dist/cjs/auth-api/auth-module.d.ts +23 -3
  7. package/dist/cjs/auth-api/auth-module.js +320 -124
  8. package/dist/cjs/auth-api/compat-auth-storage.d.ts +7 -5
  9. package/dist/cjs/auth-api/compat-auth-storage.js +15 -3
  10. package/dist/cjs/auth-api/mem-auth-store.d.ts +5 -3
  11. package/dist/cjs/auth-api/mem-auth-store.js +14 -28
  12. package/dist/cjs/auth-api/module.d.ts +1 -1
  13. package/dist/cjs/auth-api/sql-auth-store.d.ts +16 -4
  14. package/dist/cjs/auth-api/sql-auth-store.js +43 -30
  15. package/dist/cjs/auth-api/storage.d.ts +6 -4
  16. package/dist/cjs/auth-api/storage.js +15 -5
  17. package/dist/cjs/auth-api/types.d.ts +7 -2
  18. package/dist/cjs/auth-api/user-id.d.ts +5 -0
  19. package/dist/cjs/auth-api/user-id.js +38 -0
  20. package/dist/cjs/auth-cookie-options.d.ts +11 -0
  21. package/dist/cjs/auth-cookie-options.js +66 -0
  22. package/dist/cjs/index.cjs +4 -14
  23. package/dist/cjs/index.d.ts +4 -9
  24. package/dist/cjs/oauth/memory.d.ts +6 -0
  25. package/dist/cjs/oauth/memory.js +44 -11
  26. package/dist/cjs/oauth/models.d.ts +7 -2
  27. package/dist/cjs/oauth/models.js +10 -21
  28. package/dist/cjs/oauth/sequelize.d.ts +10 -48
  29. package/dist/cjs/oauth/sequelize.js +44 -99
  30. package/dist/cjs/oauth/types.d.ts +1 -0
  31. package/dist/cjs/passkey/base.d.ts +2 -0
  32. package/dist/cjs/passkey/config.d.ts +2 -0
  33. package/dist/cjs/passkey/config.js +26 -0
  34. package/dist/cjs/passkey/memory.d.ts +8 -0
  35. package/dist/cjs/passkey/memory.js +57 -16
  36. package/dist/cjs/passkey/models.d.ts +13 -4
  37. package/dist/cjs/passkey/models.js +41 -14
  38. package/dist/cjs/passkey/sequelize.d.ts +13 -25
  39. package/dist/cjs/passkey/sequelize.js +68 -153
  40. package/dist/cjs/passkey/service.d.ts +6 -2
  41. package/dist/cjs/passkey/service.js +205 -27
  42. package/dist/cjs/passkey/types.d.ts +18 -9
  43. package/dist/cjs/sequelize-utils.d.ts +8 -0
  44. package/dist/cjs/sequelize-utils.js +57 -0
  45. package/dist/cjs/token/base.d.ts +2 -1
  46. package/dist/cjs/token/base.js +3 -1
  47. package/dist/cjs/token/memory.d.ts +10 -0
  48. package/dist/cjs/token/memory.js +122 -32
  49. package/dist/cjs/token/sequelize.d.ts +4 -4
  50. package/dist/cjs/token/sequelize.js +67 -85
  51. package/dist/cjs/token/types.d.ts +8 -1
  52. package/dist/cjs/user/base.d.ts +1 -0
  53. package/dist/cjs/user/base.js +11 -4
  54. package/dist/cjs/user/memory.d.ts +2 -0
  55. package/dist/cjs/user/memory.js +9 -10
  56. package/dist/cjs/user/sequelize.d.ts +7 -2
  57. package/dist/cjs/user/sequelize.js +19 -32
  58. package/dist/esm/api-module.d.ts +7 -4
  59. package/dist/esm/api-module.js +9 -0
  60. package/dist/esm/api-server-base.d.ts +80 -23
  61. package/dist/esm/api-server-base.js +608 -100
  62. package/dist/esm/auth-api/auth-module.d.ts +23 -3
  63. package/dist/esm/auth-api/auth-module.js +321 -125
  64. package/dist/esm/auth-api/compat-auth-storage.d.ts +7 -5
  65. package/dist/esm/auth-api/compat-auth-storage.js +13 -1
  66. package/dist/esm/auth-api/mem-auth-store.d.ts +5 -3
  67. package/dist/esm/auth-api/mem-auth-store.js +14 -28
  68. package/dist/esm/auth-api/module.d.ts +1 -1
  69. package/dist/esm/auth-api/sql-auth-store.d.ts +16 -4
  70. package/dist/esm/auth-api/sql-auth-store.js +43 -30
  71. package/dist/esm/auth-api/storage.d.ts +6 -4
  72. package/dist/esm/auth-api/storage.js +13 -3
  73. package/dist/esm/auth-api/types.d.ts +7 -2
  74. package/dist/esm/auth-api/user-id.d.ts +5 -0
  75. package/dist/esm/auth-api/user-id.js +32 -0
  76. package/dist/esm/auth-cookie-options.d.ts +11 -0
  77. package/dist/esm/auth-cookie-options.js +63 -0
  78. package/dist/esm/index.d.ts +4 -9
  79. package/dist/esm/index.js +2 -7
  80. package/dist/esm/oauth/memory.d.ts +6 -0
  81. package/dist/esm/oauth/memory.js +44 -11
  82. package/dist/esm/oauth/models.d.ts +7 -2
  83. package/dist/esm/oauth/models.js +6 -19
  84. package/dist/esm/oauth/sequelize.d.ts +10 -48
  85. package/dist/esm/oauth/sequelize.js +32 -87
  86. package/dist/esm/oauth/types.d.ts +1 -0
  87. package/dist/esm/passkey/base.d.ts +2 -0
  88. package/dist/esm/passkey/config.d.ts +2 -0
  89. package/dist/esm/passkey/config.js +23 -0
  90. package/dist/esm/passkey/memory.d.ts +8 -0
  91. package/dist/esm/passkey/memory.js +57 -16
  92. package/dist/esm/passkey/models.d.ts +13 -4
  93. package/dist/esm/passkey/models.js +39 -12
  94. package/dist/esm/passkey/sequelize.d.ts +13 -25
  95. package/dist/esm/passkey/sequelize.js +69 -154
  96. package/dist/esm/passkey/service.d.ts +6 -2
  97. package/dist/esm/passkey/service.js +173 -28
  98. package/dist/esm/passkey/types.d.ts +18 -9
  99. package/dist/esm/sequelize-utils.d.ts +8 -0
  100. package/dist/esm/sequelize-utils.js +48 -0
  101. package/dist/esm/token/base.d.ts +2 -1
  102. package/dist/esm/token/base.js +3 -1
  103. package/dist/esm/token/memory.d.ts +10 -0
  104. package/dist/esm/token/memory.js +122 -32
  105. package/dist/esm/token/sequelize.d.ts +4 -4
  106. package/dist/esm/token/sequelize.js +67 -85
  107. package/dist/esm/token/types.d.ts +8 -1
  108. package/dist/esm/user/base.d.ts +1 -0
  109. package/dist/esm/user/base.js +11 -4
  110. package/dist/esm/user/memory.d.ts +2 -0
  111. package/dist/esm/user/memory.js +9 -10
  112. package/dist/esm/user/sequelize.d.ts +7 -2
  113. package/dist/esm/user/sequelize.js +19 -32
  114. package/docs/swagger/openapi.json +1876 -0
  115. package/package.json +81 -32
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizePasskeyConfig = normalizePasskeyConfig;
4
+ const DEFAULT_PASSKEY_CONFIG = {
5
+ rpId: 'localhost',
6
+ rpName: 'API Server',
7
+ origins: ['http://localhost:5173'],
8
+ timeoutMs: 5 * 60 * 1000,
9
+ userVerification: 'preferred'
10
+ };
11
+ function isOriginString(origin) {
12
+ return typeof origin === 'string' && origin.trim().length > 0;
13
+ }
14
+ function normalizePasskeyConfig(config = {}) {
15
+ const candidateOrigins = Array.isArray(config.origins) && config.origins.length > 0 ? config.origins.filter(isOriginString) : null;
16
+ return {
17
+ rpId: config.rpId?.trim() || DEFAULT_PASSKEY_CONFIG.rpId,
18
+ rpName: config.rpName?.trim() || DEFAULT_PASSKEY_CONFIG.rpName,
19
+ origins: candidateOrigins ? candidateOrigins.map((origin) => origin.trim()) : DEFAULT_PASSKEY_CONFIG.origins,
20
+ timeoutMs: typeof config.timeoutMs === 'number' && config.timeoutMs > 0
21
+ ? config.timeoutMs
22
+ : DEFAULT_PASSKEY_CONFIG.timeoutMs,
23
+ userVerification: config.userVerification ?? DEFAULT_PASSKEY_CONFIG.userVerification,
24
+ debug: Boolean(config.debug)
25
+ };
26
+ }
@@ -6,21 +6,29 @@ export interface MemoryPasskeyStoreOptions {
6
6
  userId?: AuthIdentifier;
7
7
  login?: string;
8
8
  }) => Promise<PasskeyUserDescriptor | null>;
9
+ maxCredentials?: number;
10
+ maxChallenges?: number;
9
11
  }
10
12
  export declare class MemoryPasskeyStore extends PasskeyStore {
11
13
  private readonly resolveUserFn;
12
14
  private readonly credentials;
13
15
  private readonly challenges;
16
+ private readonly maxCredentials?;
17
+ private readonly maxChallenges?;
14
18
  constructor(options: MemoryPasskeyStoreOptions);
15
19
  resolveUser(params: {
16
20
  userId?: AuthIdentifier;
17
21
  login?: string;
18
22
  }): Promise<PasskeyUserDescriptor | null>;
19
23
  listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
24
+ deleteCredential(credentialId: Buffer | string): Promise<boolean>;
20
25
  findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
21
26
  saveCredential(record: StoredPasskeyCredential): Promise<void>;
22
27
  updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
23
28
  saveChallenge(record: PasskeyChallengeRecord): Promise<void>;
29
+ getChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
24
30
  consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
25
31
  cleanupChallenges(now: Date): Promise<void>;
32
+ private enforceCredentialCapacity;
33
+ private enforceChallengeCapacity;
26
34
  }
@@ -1,23 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MemoryPasskeyStore = void 0;
4
+ const user_id_js_1 = require("../auth-api/user-id.js");
4
5
  const base_js_1 = require("./base.js");
5
6
  function encodeCredentialId(value) {
6
7
  return Buffer.isBuffer(value) ? value.toString('base64') : value;
7
8
  }
8
- function normalizeUserId(identifier) {
9
- if (typeof identifier === 'number' && Number.isFinite(identifier)) {
10
- return identifier;
11
- }
12
- if (typeof identifier === 'string' && /^\d+$/.test(identifier)) {
13
- return Number(identifier);
14
- }
15
- return identifier;
16
- }
17
9
  function cloneCredential(record) {
18
10
  return {
19
11
  ...record,
20
12
  credentialId: Buffer.isBuffer(record.credentialId) ? Buffer.from(record.credentialId) : record.credentialId,
13
+ publicKey: Buffer.from(record.publicKey),
21
14
  transports: record.transports ? [...record.transports] : undefined
22
15
  };
23
16
  }
@@ -27,16 +20,32 @@ class MemoryPasskeyStore extends base_js_1.PasskeyStore {
27
20
  this.credentials = new Map();
28
21
  this.challenges = new Map();
29
22
  this.resolveUserFn = options.resolveUser;
23
+ this.maxCredentials =
24
+ typeof options.maxCredentials === 'number' &&
25
+ Number.isFinite(options.maxCredentials) &&
26
+ options.maxCredentials > 0
27
+ ? Math.floor(options.maxCredentials)
28
+ : undefined;
29
+ this.maxChallenges =
30
+ typeof options.maxChallenges === 'number' &&
31
+ Number.isFinite(options.maxChallenges) &&
32
+ options.maxChallenges > 0
33
+ ? Math.floor(options.maxChallenges)
34
+ : undefined;
30
35
  }
31
36
  async resolveUser(params) {
32
37
  return this.resolveUserFn(params);
33
38
  }
34
39
  async listUserCredentials(userId) {
35
- const normalizedUserId = normalizeUserId(userId);
40
+ const normalizedUserId = (0, user_id_js_1.normalizeComparableUserId)(userId);
36
41
  return [...this.credentials.values()]
37
- .filter((record) => normalizeUserId(record.userId) === normalizedUserId)
42
+ .filter((record) => (0, user_id_js_1.normalizeComparableUserId)(record.userId) === normalizedUserId)
38
43
  .map((record) => cloneCredential(record));
39
44
  }
45
+ async deleteCredential(credentialId) {
46
+ const key = encodeCredentialId(credentialId);
47
+ return this.credentials.delete(key);
48
+ }
40
49
  async findCredentialById(credentialId) {
41
50
  const record = this.credentials.get(encodeCredentialId(credentialId));
42
51
  return record ? cloneCredential(record) : null;
@@ -44,10 +53,11 @@ class MemoryPasskeyStore extends base_js_1.PasskeyStore {
44
53
  async saveCredential(record) {
45
54
  this.credentials.set(encodeCredentialId(record.credentialId), {
46
55
  ...record,
47
- userId: normalizeUserId(record.userId),
56
+ userId: (0, user_id_js_1.normalizeComparableUserId)(record.userId),
48
57
  credentialId: Buffer.isBuffer(record.credentialId) ? Buffer.from(record.credentialId) : record.credentialId,
49
58
  transports: record.transports ? [...record.transports] : undefined
50
59
  });
60
+ this.enforceCredentialCapacity();
51
61
  }
52
62
  async updateCredentialCounter(credentialId, counter) {
53
63
  const key = encodeCredentialId(credentialId);
@@ -58,10 +68,17 @@ class MemoryPasskeyStore extends base_js_1.PasskeyStore {
58
68
  }
59
69
  async saveChallenge(record) {
60
70
  this.challenges.set(record.challenge, {
61
- ...record,
62
- userId: record.userId !== undefined ? normalizeUserId(record.userId) : undefined,
63
- metadata: record.metadata ? { ...record.metadata } : {}
71
+ challenge: record.challenge,
72
+ action: record.action,
73
+ userId: record.userId !== undefined ? (0, user_id_js_1.normalizeComparableUserId)(record.userId) : undefined,
74
+ login: record.login ?? undefined,
75
+ expiresAt: record.expiresAt
64
76
  });
77
+ this.enforceChallengeCapacity();
78
+ }
79
+ async getChallenge(challenge) {
80
+ const record = this.challenges.get(challenge);
81
+ return record ? { ...record } : null;
65
82
  }
66
83
  async consumeChallenge(challenge) {
67
84
  const record = this.challenges.get(challenge);
@@ -69,7 +86,7 @@ class MemoryPasskeyStore extends base_js_1.PasskeyStore {
69
86
  return null;
70
87
  }
71
88
  this.challenges.delete(challenge);
72
- return { ...record, metadata: record.metadata ? { ...record.metadata } : {} };
89
+ return { ...record };
73
90
  }
74
91
  async cleanupChallenges(now) {
75
92
  for (const [challenge, record] of this.challenges.entries()) {
@@ -78,5 +95,29 @@ class MemoryPasskeyStore extends base_js_1.PasskeyStore {
78
95
  }
79
96
  }
80
97
  }
98
+ enforceCredentialCapacity() {
99
+ if (!this.maxCredentials) {
100
+ return;
101
+ }
102
+ while (this.credentials.size > this.maxCredentials) {
103
+ const oldest = this.credentials.keys().next().value;
104
+ if (!oldest) {
105
+ return;
106
+ }
107
+ this.credentials.delete(oldest);
108
+ }
109
+ }
110
+ enforceChallengeCapacity() {
111
+ if (!this.maxChallenges) {
112
+ return;
113
+ }
114
+ while (this.challenges.size > this.maxChallenges) {
115
+ const oldest = this.challenges.keys().next().value;
116
+ if (!oldest) {
117
+ return;
118
+ }
119
+ this.challenges.delete(oldest);
120
+ }
121
+ }
81
122
  }
82
123
  exports.MemoryPasskeyStore = MemoryPasskeyStore;
@@ -1,5 +1,4 @@
1
1
  import { Model, type InferAttributes, type InferCreationAttributes, type ModelStatic, type Sequelize } from 'sequelize';
2
- import type { PasskeyChallengeMetadata } from './service.js';
3
2
  export declare class PasskeyCredentialModel extends Model<InferAttributes<PasskeyCredentialModel>, InferCreationAttributes<PasskeyCredentialModel>> {
4
3
  credentialId: Buffer;
5
4
  userId: number;
@@ -8,6 +7,13 @@ export declare class PasskeyCredentialModel extends Model<InferAttributes<Passke
8
7
  transports: string[] | null;
9
8
  backedUp: boolean;
10
9
  deviceType: string;
10
+ label: string | null;
11
+ createdDomain: string | null;
12
+ createdUserAgent: string | null;
13
+ createdBrowser: string | null;
14
+ createdOs: string | null;
15
+ createdDevice: string | null;
16
+ createdIp: string | null;
11
17
  createdAt?: Date;
12
18
  updatedAt?: Date;
13
19
  }
@@ -16,10 +22,13 @@ export declare class PasskeyChallengeModel extends Model<InferAttributes<Passkey
16
22
  action: 'register' | 'authenticate';
17
23
  userId: number | null;
18
24
  login: string | null;
19
- metadata: PasskeyChallengeMetadata | null;
20
25
  expiresAt: Date;
21
26
  createdAt?: Date;
22
27
  updatedAt?: Date;
23
28
  }
24
- export declare function initPasskeyCredentialModel(sequelize: Sequelize): ModelStatic<PasskeyCredentialModel>;
25
- export declare function initPasskeyChallengeModel(sequelize: Sequelize): ModelStatic<PasskeyChallengeModel>;
29
+ export declare function initPasskeyCredentialModel(sequelize: Sequelize, options?: {
30
+ tablePrefix?: string;
31
+ }): ModelStatic<PasskeyCredentialModel>;
32
+ export declare function initPasskeyChallengeModel(sequelize: Sequelize, options?: {
33
+ tablePrefix?: string;
34
+ }): ModelStatic<PasskeyChallengeModel>;
@@ -4,18 +4,15 @@ exports.PasskeyChallengeModel = exports.PasskeyCredentialModel = void 0;
4
4
  exports.initPasskeyCredentialModel = initPasskeyCredentialModel;
5
5
  exports.initPasskeyChallengeModel = initPasskeyChallengeModel;
6
6
  const sequelize_1 = require("sequelize");
7
- const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
8
- function integerIdType(sequelize) {
9
- return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
10
- }
7
+ const sequelize_utils_js_1 = require("../sequelize-utils.js");
11
8
  class PasskeyCredentialModel extends sequelize_1.Model {
12
9
  }
13
10
  exports.PasskeyCredentialModel = PasskeyCredentialModel;
14
11
  class PasskeyChallengeModel extends sequelize_1.Model {
15
12
  }
16
13
  exports.PasskeyChallengeModel = PasskeyChallengeModel;
17
- function initPasskeyCredentialModel(sequelize) {
18
- const idType = integerIdType(sequelize);
14
+ function initPasskeyCredentialModel(sequelize, options = {}) {
15
+ const idType = (0, sequelize_utils_js_1.integerIdType)(sequelize);
19
16
  return PasskeyCredentialModel.init({
20
17
  credentialId: {
21
18
  field: 'credential_id',
@@ -67,16 +64,50 @@ function initPasskeyCredentialModel(sequelize) {
67
64
  type: sequelize_1.DataTypes.STRING(32),
68
65
  allowNull: false,
69
66
  defaultValue: 'multiDevice'
67
+ },
68
+ label: {
69
+ type: sequelize_1.DataTypes.STRING(120),
70
+ allowNull: true
71
+ },
72
+ createdDomain: {
73
+ field: 'created_domain',
74
+ type: sequelize_1.DataTypes.STRING(255),
75
+ allowNull: true
76
+ },
77
+ createdUserAgent: {
78
+ field: 'created_user_agent',
79
+ type: sequelize_1.DataTypes.TEXT,
80
+ allowNull: true
81
+ },
82
+ createdBrowser: {
83
+ field: 'created_browser',
84
+ type: sequelize_1.DataTypes.STRING(120),
85
+ allowNull: true
86
+ },
87
+ createdOs: {
88
+ field: 'created_os',
89
+ type: sequelize_1.DataTypes.STRING(120),
90
+ allowNull: true
91
+ },
92
+ createdDevice: {
93
+ field: 'created_device',
94
+ type: sequelize_1.DataTypes.STRING(120),
95
+ allowNull: true
96
+ },
97
+ createdIp: {
98
+ field: 'created_ip',
99
+ type: sequelize_1.DataTypes.STRING(45),
100
+ allowNull: true
70
101
  }
71
102
  }, {
72
103
  sequelize,
73
- tableName: 'passkey_credentials',
104
+ tableName: (0, sequelize_utils_js_1.applyTablePrefix)(options.tablePrefix, 'passkey_credentials'),
74
105
  timestamps: true,
75
106
  underscored: true
76
107
  });
77
108
  }
78
- function initPasskeyChallengeModel(sequelize) {
79
- const idType = integerIdType(sequelize);
109
+ function initPasskeyChallengeModel(sequelize, options = {}) {
110
+ const idType = (0, sequelize_utils_js_1.integerIdType)(sequelize);
80
111
  return PasskeyChallengeModel.init({
81
112
  challenge: {
82
113
  type: sequelize_1.DataTypes.STRING(255),
@@ -96,10 +127,6 @@ function initPasskeyChallengeModel(sequelize) {
96
127
  type: sequelize_1.DataTypes.STRING(128),
97
128
  allowNull: true
98
129
  },
99
- metadata: {
100
- type: sequelize_1.DataTypes.JSON,
101
- allowNull: true
102
- },
103
130
  expiresAt: {
104
131
  field: 'expires_at',
105
132
  type: sequelize_1.DataTypes.DATE,
@@ -107,7 +134,7 @@ function initPasskeyChallengeModel(sequelize) {
107
134
  }
108
135
  }, {
109
136
  sequelize,
110
- tableName: 'passkey_challenges',
137
+ tableName: (0, sequelize_utils_js_1.applyTablePrefix)(options.tablePrefix, 'passkey_challenges'),
111
138
  timestamps: true,
112
139
  underscored: true,
113
140
  indexes: [{ fields: ['expires_at'] }, { fields: ['user_id'] }]
@@ -1,34 +1,19 @@
1
- import { Model, type InferAttributes, type InferCreationAttributes, type ModelStatic, type Sequelize } from 'sequelize';
1
+ import { type ModelStatic, type Sequelize } from 'sequelize';
2
2
  import { PasskeyStore } from './base.js';
3
+ import { PasskeyChallengeModel, PasskeyCredentialModel } from './models.js';
3
4
  import type { PasskeyChallengeRecord, PasskeyUserDescriptor, StoredPasskeyCredential } from './types.js';
4
5
  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
6
  export interface SequelizePasskeyStoreOptions {
27
7
  sequelize: Sequelize;
8
+ tablePrefix?: string;
28
9
  credentialModel?: ModelStatic<PasskeyCredentialModel>;
29
10
  challengeModel?: ModelStatic<PasskeyChallengeModel>;
30
- credentialModelFactory?: (sequelize: Sequelize) => ModelStatic<PasskeyCredentialModel>;
31
- challengeModelFactory?: (sequelize: Sequelize) => ModelStatic<PasskeyChallengeModel>;
11
+ credentialModelFactory?: (sequelize: Sequelize, options?: {
12
+ tablePrefix?: string;
13
+ }) => ModelStatic<PasskeyCredentialModel>;
14
+ challengeModelFactory?: (sequelize: Sequelize, options?: {
15
+ tablePrefix?: string;
16
+ }) => ModelStatic<PasskeyChallengeModel>;
32
17
  resolveUser: (params: {
33
18
  userId?: AuthIdentifier;
34
19
  login?: string;
@@ -44,11 +29,14 @@ export declare class SequelizePasskeyStore extends PasskeyStore {
44
29
  login?: string;
45
30
  }): Promise<PasskeyUserDescriptor | null>;
46
31
  listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
32
+ deleteCredential(credentialId: Buffer | string): Promise<boolean>;
47
33
  findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
48
34
  saveCredential(record: StoredPasskeyCredential): Promise<void>;
49
35
  updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
50
36
  saveChallenge(record: PasskeyChallengeRecord): Promise<void>;
37
+ getChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
51
38
  consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
52
39
  cleanupChallenges(now: Date): Promise<void>;
40
+ private toChallengeRecord;
41
+ private toStoredCredential;
53
42
  }
54
- export {};
@@ -2,126 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SequelizePasskeyStore = void 0;
4
4
  const sequelize_1 = require("sequelize");
5
+ const user_id_js_1 = require("../auth-api/user-id.js");
5
6
  const base_js_1 = require("./base.js");
6
- const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
7
- function integerIdType(sequelize) {
8
- return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
9
- }
7
+ const models_js_1 = require("./models.js");
10
8
  function encodeCredentialId(value) {
11
9
  return Buffer.isBuffer(value) ? value.toString('base64') : value;
12
10
  }
13
- function normalizeUserId(identifier) {
14
- if (typeof identifier === 'number' && Number.isFinite(identifier)) {
15
- return identifier;
16
- }
17
- if (typeof identifier === 'string' && /^\d+$/.test(identifier)) {
18
- return Number(identifier);
19
- }
20
- throw new Error(`Unable to normalise user identifier: ${identifier}`);
21
- }
22
- class PasskeyCredentialModel extends sequelize_1.Model {
23
- }
24
- class PasskeyChallengeModel extends sequelize_1.Model {
25
- }
26
- function initPasskeyCredentialModel(sequelize) {
27
- const idType = integerIdType(sequelize);
28
- return PasskeyCredentialModel.init({
29
- credentialId: {
30
- field: 'credential_id',
31
- type: sequelize_1.DataTypes.STRING(768),
32
- primaryKey: true,
33
- allowNull: false,
34
- get() {
35
- const raw = this.getDataValue('credentialId');
36
- if (!raw) {
37
- return raw;
38
- }
39
- if (Buffer.isBuffer(raw)) {
40
- return raw;
41
- }
42
- return Buffer.from(raw, 'base64');
43
- },
44
- set(value) {
45
- const encoded = typeof value === 'string' ? value : value.toString('base64');
46
- this.setDataValue('credentialId', encoded);
47
- }
48
- },
49
- userId: {
50
- field: 'user_id',
51
- type: idType,
52
- allowNull: false
53
- },
54
- publicKey: {
55
- field: 'public_key',
56
- type: sequelize_1.DataTypes.BLOB,
57
- allowNull: false
58
- },
59
- counter: {
60
- type: sequelize_1.DataTypes.INTEGER,
61
- allowNull: false,
62
- defaultValue: 0
63
- },
64
- transports: {
65
- type: sequelize_1.DataTypes.JSON,
66
- allowNull: true
67
- },
68
- backedUp: {
69
- field: 'backed_up',
70
- type: sequelize_1.DataTypes.BOOLEAN,
71
- allowNull: false,
72
- defaultValue: false
73
- },
74
- deviceType: {
75
- field: 'device_type',
76
- type: sequelize_1.DataTypes.STRING(32),
77
- allowNull: false,
78
- defaultValue: 'multiDevice'
79
- }
80
- }, {
81
- sequelize,
82
- tableName: 'passkey_credentials',
83
- timestamps: true,
84
- underscored: true
85
- });
86
- }
87
- function initPasskeyChallengeModel(sequelize) {
88
- const idType = integerIdType(sequelize);
89
- return PasskeyChallengeModel.init({
90
- challenge: {
91
- type: sequelize_1.DataTypes.STRING(255),
92
- primaryKey: true,
93
- allowNull: false
94
- },
95
- action: {
96
- type: sequelize_1.DataTypes.STRING(16),
97
- allowNull: false
98
- },
99
- userId: {
100
- field: 'user_id',
101
- type: idType,
102
- allowNull: true
103
- },
104
- login: {
105
- type: sequelize_1.DataTypes.STRING(128),
106
- allowNull: true
107
- },
108
- metadata: {
109
- type: sequelize_1.DataTypes.JSON,
110
- allowNull: true
111
- },
112
- expiresAt: {
113
- field: 'expires_at',
114
- type: sequelize_1.DataTypes.DATE,
115
- allowNull: false
116
- }
117
- }, {
118
- sequelize,
119
- tableName: 'passkey_challenges',
120
- timestamps: true,
121
- underscored: true,
122
- indexes: [{ fields: ['expires_at'] }, { fields: ['user_id'] }]
123
- });
124
- }
125
11
  class SequelizePasskeyStore extends base_js_1.PasskeyStore {
126
12
  constructor(options) {
127
13
  super();
@@ -131,49 +17,47 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
131
17
  this.resolveUserFn = options.resolveUser;
132
18
  this.credentials =
133
19
  options.credentialModel ??
134
- (options.credentialModelFactory ?? initPasskeyCredentialModel)(options.sequelize);
20
+ (options.credentialModelFactory ?? models_js_1.initPasskeyCredentialModel)(options.sequelize, {
21
+ tablePrefix: options.tablePrefix
22
+ });
135
23
  this.challenges =
136
- options.challengeModel ?? (options.challengeModelFactory ?? initPasskeyChallengeModel)(options.sequelize);
24
+ options.challengeModel ??
25
+ (options.challengeModelFactory ?? models_js_1.initPasskeyChallengeModel)(options.sequelize, {
26
+ tablePrefix: options.tablePrefix
27
+ });
137
28
  }
138
29
  async resolveUser(params) {
139
30
  return this.resolveUserFn(params);
140
31
  }
141
32
  async listUserCredentials(userId) {
142
- const models = await this.credentials.findAll({ where: { userId: normalizeUserId(userId) } });
143
- return models.map((model) => ({
144
- userId: model.userId,
145
- credentialId: model.credentialId,
146
- publicKey: model.publicKey,
147
- counter: model.counter,
148
- transports: (model.transports ?? undefined),
149
- backedUp: model.backedUp,
150
- deviceType: model.deviceType
151
- }));
33
+ const models = await this.credentials.findAll({ where: { userId: (0, user_id_js_1.normalizeNumericUserId)(userId) } });
34
+ return models.map((model) => this.toStoredCredential(model));
35
+ }
36
+ async deleteCredential(credentialId) {
37
+ const encoded = Buffer.isBuffer(credentialId) ? credentialId.toString('base64') : credentialId;
38
+ const deleted = await this.credentials.destroy({ where: { credentialId: encoded } });
39
+ return deleted > 0;
152
40
  }
153
41
  async findCredentialById(credentialId) {
154
42
  const model = await this.credentials.findByPk(encodeCredentialId(credentialId));
155
- if (!model) {
156
- return null;
157
- }
158
- return {
159
- userId: model.userId,
160
- credentialId: model.credentialId,
161
- publicKey: model.publicKey,
162
- counter: model.counter,
163
- transports: (model.transports ?? undefined),
164
- backedUp: model.backedUp,
165
- deviceType: model.deviceType
166
- };
43
+ return model ? this.toStoredCredential(model) : null;
167
44
  }
168
45
  async saveCredential(record) {
169
46
  await this.credentials.upsert({
170
47
  credentialId: record.credentialId,
171
- userId: normalizeUserId(record.userId),
48
+ userId: (0, user_id_js_1.normalizeNumericUserId)(record.userId),
172
49
  publicKey: record.publicKey,
173
50
  counter: record.counter,
174
51
  transports: record.transports ?? null,
175
52
  backedUp: record.backedUp,
176
- deviceType: record.deviceType
53
+ deviceType: record.deviceType,
54
+ label: record.label ?? null,
55
+ createdDomain: record.createdDomain ?? null,
56
+ createdUserAgent: record.createdUserAgent ?? null,
57
+ createdBrowser: record.createdBrowser ?? null,
58
+ createdOs: record.createdOs ?? null,
59
+ createdDevice: record.createdDevice ?? null,
60
+ createdIp: record.createdIp ?? null
177
61
  });
178
62
  }
179
63
  async updateCredentialCounter(credentialId, counter) {
@@ -183,29 +67,60 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
183
67
  await this.challenges.upsert({
184
68
  challenge: record.challenge,
185
69
  action: record.action,
186
- userId: record.userId !== undefined ? normalizeUserId(record.userId) : null,
70
+ userId: record.userId !== undefined ? (0, user_id_js_1.normalizeNumericUserId)(record.userId) : null,
187
71
  login: record.login ?? null,
188
- metadata: (record.metadata ?? {}),
189
72
  expiresAt: record.expiresAt
190
73
  });
191
74
  }
192
- async consumeChallenge(challenge) {
75
+ async getChallenge(challenge) {
193
76
  const model = await this.challenges.findByPk(challenge);
194
- if (!model) {
195
- return null;
77
+ return model ? this.toChallengeRecord(model) : null;
78
+ }
79
+ async consumeChallenge(challenge) {
80
+ const sequelize = this.challenges.sequelize;
81
+ if (!sequelize) {
82
+ throw new Error('Challenge model is not bound to a Sequelize instance');
196
83
  }
197
- await model.destroy();
84
+ return sequelize.transaction({ isolationLevel: sequelize_1.Transaction.ISOLATION_LEVELS.READ_COMMITTED }, async (transaction) => {
85
+ const model = await this.challenges.findByPk(challenge, { transaction, lock: true });
86
+ if (!model) {
87
+ return null;
88
+ }
89
+ await model.destroy({ transaction });
90
+ return this.toChallengeRecord(model);
91
+ });
92
+ }
93
+ async cleanupChallenges(now) {
94
+ await this.challenges.destroy({ where: { expiresAt: { [sequelize_1.Op.lte]: now } } });
95
+ }
96
+ toChallengeRecord(model) {
198
97
  return {
199
98
  challenge: model.challenge,
200
99
  action: model.action,
201
- userId: model.userId ?? undefined,
100
+ userId: model.userId !== null ? String(model.userId) : undefined,
202
101
  login: model.login ?? undefined,
203
- expiresAt: model.expiresAt,
204
- metadata: model.metadata ?? {}
102
+ expiresAt: model.expiresAt
205
103
  };
206
104
  }
207
- async cleanupChallenges(now) {
208
- await this.challenges.destroy({ where: { expiresAt: { [sequelize_1.Op.lte]: now } } });
105
+ toStoredCredential(model) {
106
+ return {
107
+ userId: String(model.userId),
108
+ credentialId: model.credentialId,
109
+ publicKey: model.publicKey,
110
+ counter: model.counter,
111
+ transports: (model.transports ?? undefined),
112
+ backedUp: model.backedUp,
113
+ deviceType: model.deviceType,
114
+ label: model.label ?? undefined,
115
+ createdDomain: model.createdDomain ?? undefined,
116
+ createdUserAgent: model.createdUserAgent ?? undefined,
117
+ createdBrowser: model.createdBrowser ?? undefined,
118
+ createdOs: model.createdOs ?? undefined,
119
+ createdDevice: model.createdDevice ?? undefined,
120
+ createdIp: model.createdIp ?? undefined,
121
+ createdAt: model.createdAt ?? undefined,
122
+ updatedAt: model.updatedAt ?? undefined
123
+ };
209
124
  }
210
125
  }
211
126
  exports.SequelizePasskeyStore = SequelizePasskeyStore;
@@ -1,11 +1,14 @@
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';
1
+ import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyStorageAdapter, PasskeyVerificationParams, PasskeyVerificationResult, PasskeyServiceConfig, StoredPasskeyCredential } from './types.js';
2
+ import type { AuthIdentifier } from '../auth-api/types.js';
3
+ export type { CredentialDeviceType, PasskeyChallenge, PasskeyChallengeParams, PasskeyChallengeRecord, PasskeyUserDescriptor, PasskeyVerificationParams, PasskeyVerificationResult, PasskeyServiceConfig, PasskeyStorageAdapter, StoredPasskeyCredential } from './types.js';
3
4
  type Logger = Pick<typeof console, 'error' | 'warn'>;
4
5
  export declare class PasskeyService {
5
6
  private readonly config;
6
7
  private readonly adapter;
7
8
  private readonly logger;
8
9
  constructor(config: PasskeyServiceConfig, adapter: PasskeyStorageAdapter, logger?: Logger);
10
+ listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
11
+ deleteCredential(credentialId: Buffer | string): Promise<boolean>;
9
12
  createChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
10
13
  verifyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
11
14
  private createRegistrationChallenge;
@@ -14,4 +17,5 @@ export declare class PasskeyService {
14
17
  private verifyAuthentication;
15
18
  private requireUser;
16
19
  private createExpiry;
20
+ private requireUserVerification;
17
21
  }