@technomoron/api-server-base 2.0.0-beta.17 → 2.0.0-beta.19
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/README.txt +48 -35
- package/dist/cjs/api-module.cjs +9 -0
- package/dist/cjs/api-module.d.ts +4 -2
- package/dist/cjs/api-server-base.cjs +178 -57
- package/dist/cjs/api-server-base.d.ts +31 -2
- package/dist/cjs/auth-api/auth-module.d.ts +12 -1
- package/dist/cjs/auth-api/auth-module.js +77 -35
- package/dist/cjs/auth-api/mem-auth-store.js +2 -23
- package/dist/cjs/auth-api/sql-auth-store.js +4 -31
- package/dist/cjs/auth-api/user-id.d.ts +4 -0
- package/dist/cjs/auth-api/user-id.js +31 -0
- package/dist/cjs/auth-cookie-options.d.ts +11 -0
- package/dist/cjs/auth-cookie-options.js +57 -0
- package/dist/cjs/oauth/memory.js +4 -10
- package/dist/cjs/oauth/models.js +4 -15
- package/dist/cjs/oauth/sequelize.js +8 -23
- package/dist/cjs/passkey/config.d.ts +2 -0
- package/dist/cjs/passkey/config.js +26 -0
- package/dist/cjs/passkey/memory.js +2 -9
- package/dist/cjs/passkey/models.js +4 -15
- package/dist/cjs/passkey/sequelize.js +6 -22
- package/dist/cjs/passkey/service.js +1 -1
- package/dist/cjs/passkey/types.d.ts +5 -0
- package/dist/cjs/sequelize-utils.d.ts +3 -0
- package/dist/cjs/sequelize-utils.js +17 -0
- package/dist/cjs/token/memory.d.ts +4 -0
- package/dist/cjs/token/memory.js +90 -25
- package/dist/cjs/token/sequelize.js +16 -22
- package/dist/cjs/token/types.d.ts +7 -0
- package/dist/cjs/user/memory.js +2 -9
- package/dist/cjs/user/sequelize.js +6 -22
- package/dist/esm/api-module.d.ts +4 -2
- package/dist/esm/api-module.js +9 -0
- package/dist/esm/api-server-base.d.ts +31 -2
- package/dist/esm/api-server-base.js +178 -57
- package/dist/esm/auth-api/auth-module.d.ts +12 -1
- package/dist/esm/auth-api/auth-module.js +77 -35
- package/dist/esm/auth-api/mem-auth-store.js +1 -22
- package/dist/esm/auth-api/sql-auth-store.js +2 -29
- package/dist/esm/auth-api/user-id.d.ts +4 -0
- package/dist/esm/auth-api/user-id.js +26 -0
- package/dist/esm/auth-cookie-options.d.ts +11 -0
- package/dist/esm/auth-cookie-options.js +54 -0
- package/dist/esm/oauth/memory.js +4 -10
- package/dist/esm/oauth/models.js +1 -12
- package/dist/esm/oauth/sequelize.js +5 -20
- package/dist/esm/passkey/config.d.ts +2 -0
- package/dist/esm/passkey/config.js +23 -0
- package/dist/esm/passkey/memory.js +2 -9
- package/dist/esm/passkey/models.js +1 -12
- package/dist/esm/passkey/sequelize.js +3 -19
- package/dist/esm/passkey/service.js +1 -1
- package/dist/esm/passkey/types.d.ts +5 -0
- package/dist/esm/sequelize-utils.d.ts +3 -0
- package/dist/esm/sequelize-utils.js +12 -0
- package/dist/esm/token/memory.d.ts +4 -0
- package/dist/esm/token/memory.js +90 -25
- package/dist/esm/token/sequelize.js +12 -18
- package/dist/esm/token/types.d.ts +7 -0
- package/dist/esm/user/memory.js +2 -9
- package/dist/esm/user/sequelize.js +3 -19
- package/docs/swagger/openapi.json +11 -145
- package/package.json +12 -12
package/dist/esm/token/memory.js
CHANGED
|
@@ -6,12 +6,12 @@ function comparableUserId(value) {
|
|
|
6
6
|
return String(value);
|
|
7
7
|
}
|
|
8
8
|
function cloneToken(record) {
|
|
9
|
-
// this.normalizeToken is not available in static context; caller passes through instance.
|
|
10
|
-
// cloning handled via store instance methods.
|
|
11
|
-
const normalized = record;
|
|
12
9
|
return {
|
|
13
|
-
...
|
|
14
|
-
scope:
|
|
10
|
+
...record,
|
|
11
|
+
scope: record.scope ? [...record.scope] : undefined,
|
|
12
|
+
expires: record.expires ? new Date(record.expires) : undefined,
|
|
13
|
+
issuedAt: record.issuedAt ? new Date(record.issuedAt) : undefined,
|
|
14
|
+
lastSeenAt: record.lastSeenAt ? new Date(record.lastSeenAt) : undefined
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
function matchesQuery(record, query, includeExpired) {
|
|
@@ -47,15 +47,57 @@ function matchesQuery(record, query, includeExpired) {
|
|
|
47
47
|
export class MemoryTokenStore extends TokenStore {
|
|
48
48
|
constructor() {
|
|
49
49
|
super(...arguments);
|
|
50
|
-
this.tokens =
|
|
50
|
+
this.tokens = new Map();
|
|
51
|
+
this.tokensByUser = new Map();
|
|
52
|
+
}
|
|
53
|
+
indexToken(token) {
|
|
54
|
+
const userId = comparableUserId(token.userId);
|
|
55
|
+
if (!userId) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let userTokens = this.tokensByUser.get(userId);
|
|
59
|
+
if (!userTokens) {
|
|
60
|
+
userTokens = new Set();
|
|
61
|
+
this.tokensByUser.set(userId, userTokens);
|
|
62
|
+
}
|
|
63
|
+
userTokens.add(token.refreshToken);
|
|
64
|
+
}
|
|
65
|
+
unindexToken(token) {
|
|
66
|
+
const userId = comparableUserId(token.userId);
|
|
67
|
+
if (!userId) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const userTokens = this.tokensByUser.get(userId);
|
|
71
|
+
if (!userTokens) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
userTokens.delete(token.refreshToken);
|
|
75
|
+
if (userTokens.size === 0) {
|
|
76
|
+
this.tokensByUser.delete(userId);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
removeByRefreshToken(refreshToken) {
|
|
80
|
+
const existing = this.tokens.get(refreshToken);
|
|
81
|
+
if (!existing) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.unindexToken(existing);
|
|
85
|
+
this.tokens.delete(refreshToken);
|
|
51
86
|
}
|
|
52
87
|
async save(record) {
|
|
53
88
|
const stored = this.normalizeToken(record);
|
|
54
89
|
const normalizedUserId = comparableUserId(stored.userId);
|
|
90
|
+
if (!normalizedUserId) {
|
|
91
|
+
throw new Error('userId is required');
|
|
92
|
+
}
|
|
55
93
|
const domainProvided = record.domain !== undefined;
|
|
56
94
|
const fingerprintProvided = record.fingerprint !== undefined;
|
|
57
|
-
|
|
58
|
-
|
|
95
|
+
const userRefreshTokens = [...(this.tokensByUser.get(normalizedUserId) ?? [])];
|
|
96
|
+
for (const refreshToken of userRefreshTokens) {
|
|
97
|
+
const existing = this.tokens.get(refreshToken);
|
|
98
|
+
if (!existing) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
59
101
|
if (comparableUserId(existing.userId) !== normalizedUserId) {
|
|
60
102
|
continue;
|
|
61
103
|
}
|
|
@@ -68,44 +110,51 @@ export class MemoryTokenStore extends TokenStore {
|
|
|
68
110
|
if (fingerprintProvided && existing.fingerprint !== stored.fingerprint) {
|
|
69
111
|
continue;
|
|
70
112
|
}
|
|
71
|
-
this.
|
|
113
|
+
this.removeByRefreshToken(existing.refreshToken);
|
|
72
114
|
}
|
|
73
|
-
this.
|
|
115
|
+
this.removeByRefreshToken(stored.refreshToken);
|
|
116
|
+
this.tokens.set(stored.refreshToken, stored);
|
|
117
|
+
this.indexToken(stored);
|
|
74
118
|
}
|
|
75
119
|
async get(query, opts) {
|
|
76
120
|
if (!query.refreshToken && !query.accessToken && query.userId === undefined) {
|
|
77
121
|
throw new Error('At least one token lookup field must be provided');
|
|
78
122
|
}
|
|
79
123
|
const includeExpired = opts?.includeExpired ?? false;
|
|
80
|
-
|
|
81
|
-
|
|
124
|
+
if (query.refreshToken) {
|
|
125
|
+
const record = this.tokens.get(query.refreshToken);
|
|
126
|
+
return record && matchesQuery(record, query, includeExpired) ? cloneToken(record) : null;
|
|
127
|
+
}
|
|
128
|
+
for (const token of this.tokens.values()) {
|
|
129
|
+
if (matchesQuery(token, query, includeExpired)) {
|
|
130
|
+
return cloneToken(token);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
82
134
|
}
|
|
83
135
|
async delete(query) {
|
|
84
136
|
if (!query.refreshToken && !query.accessToken && query.userId === undefined && !query.clientId) {
|
|
85
137
|
return 0;
|
|
86
138
|
}
|
|
87
139
|
let removed = 0;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
140
|
+
const refreshTokens = [...this.tokens.keys()];
|
|
141
|
+
for (const refreshToken of refreshTokens) {
|
|
142
|
+
const token = this.tokens.get(refreshToken);
|
|
143
|
+
if (token && matchesQuery(token, query, true)) {
|
|
144
|
+
this.removeByRefreshToken(refreshToken);
|
|
91
145
|
removed += 1;
|
|
92
146
|
}
|
|
93
147
|
}
|
|
94
148
|
return removed;
|
|
95
149
|
}
|
|
96
150
|
async update(params) {
|
|
97
|
-
const token = this.tokens.
|
|
98
|
-
if (record.refreshToken !== params.refreshToken) {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
if (params.clientId && record.clientId !== params.clientId) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
return true;
|
|
105
|
-
});
|
|
151
|
+
const token = this.tokens.get(params.refreshToken);
|
|
106
152
|
if (!token) {
|
|
107
153
|
return false;
|
|
108
154
|
}
|
|
155
|
+
if (params.clientId && token.clientId !== params.clientId) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
109
158
|
const merged = { ...token };
|
|
110
159
|
const maybeAssign = (key) => {
|
|
111
160
|
const value = params[key];
|
|
@@ -129,12 +178,28 @@ export class MemoryTokenStore extends TokenStore {
|
|
|
129
178
|
maybeAssign('lastSeenAt');
|
|
130
179
|
maybeAssign('sessionCookie');
|
|
131
180
|
const normalized = this.normalizeToken(merged);
|
|
181
|
+
const previousUserId = token.userId;
|
|
182
|
+
const previousRefreshToken = token.refreshToken;
|
|
183
|
+
const userChanged = comparableUserId(previousUserId) !== comparableUserId(normalized.userId);
|
|
132
184
|
Object.assign(token, normalized);
|
|
185
|
+
if (userChanged || previousRefreshToken !== token.refreshToken) {
|
|
186
|
+
this.unindexToken({ ...token, userId: previousUserId, refreshToken: previousRefreshToken });
|
|
187
|
+
this.indexToken(token);
|
|
188
|
+
if (previousRefreshToken !== token.refreshToken) {
|
|
189
|
+
this.tokens.delete(previousRefreshToken);
|
|
190
|
+
this.tokens.set(token.refreshToken, token);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
133
193
|
return true;
|
|
134
194
|
}
|
|
135
195
|
async list(userId, opts = {}) {
|
|
136
196
|
const includeExpired = opts.includeExpired ?? false;
|
|
137
|
-
const
|
|
197
|
+
const normalizedUserId = comparableUserId(userId);
|
|
198
|
+
const userRefreshTokens = normalizedUserId ? [...(this.tokensByUser.get(normalizedUserId) ?? [])] : [];
|
|
199
|
+
const filtered = userRefreshTokens
|
|
200
|
+
.map((refreshToken) => this.tokens.get(refreshToken))
|
|
201
|
+
.filter((token) => Boolean(token))
|
|
202
|
+
.filter((token) => matchesQuery(token, { userId: normalizedUserId }, includeExpired));
|
|
138
203
|
const offset = opts.offset ?? 0;
|
|
139
204
|
const limit = opts.limit ?? filtered.length;
|
|
140
205
|
return filtered.slice(offset, offset + limit).map(cloneToken);
|
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
import { DataTypes, Model, Op } from 'sequelize';
|
|
2
|
+
import { normalizeStringUserId } from '../auth-api/user-id.js';
|
|
3
|
+
import { DIALECTS_SUPPORTING_UNSIGNED, applyTablePrefix } from '../sequelize-utils.js';
|
|
2
4
|
import { TokenStore } from './base.js';
|
|
3
|
-
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
4
|
-
function normalizeTablePrefix(prefix) {
|
|
5
|
-
if (!prefix) {
|
|
6
|
-
return undefined;
|
|
7
|
-
}
|
|
8
|
-
const trimmed = prefix.trim();
|
|
9
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
10
|
-
}
|
|
11
|
-
function applyTablePrefix(prefix, tableName) {
|
|
12
|
-
const normalized = normalizeTablePrefix(prefix);
|
|
13
|
-
return normalized ? `${normalized}${tableName}` : tableName;
|
|
14
|
-
}
|
|
15
5
|
class TokenModel extends Model {
|
|
16
6
|
}
|
|
17
7
|
function tokenTableOptions(sequelize, tablePrefix) {
|
|
@@ -64,11 +54,11 @@ function initTokenModel(sequelize, options = {}) {
|
|
|
64
54
|
defaultValue: DataTypes.NOW
|
|
65
55
|
},
|
|
66
56
|
access: {
|
|
67
|
-
type: DataTypes.STRING(
|
|
57
|
+
type: DataTypes.STRING(768),
|
|
68
58
|
allowNull: false
|
|
69
59
|
},
|
|
70
60
|
refresh: {
|
|
71
|
-
type: DataTypes.STRING(
|
|
61
|
+
type: DataTypes.STRING(768),
|
|
72
62
|
allowNull: false
|
|
73
63
|
},
|
|
74
64
|
domain: {
|
|
@@ -181,6 +171,13 @@ export class SequelizeTokenStore extends TokenStore {
|
|
|
181
171
|
removalWhere.client_id = normalized.clientId;
|
|
182
172
|
}
|
|
183
173
|
await this.Tokens.destroy({ where: removalWhere });
|
|
174
|
+
// Access/refresh columns are unique. Remove stale collisions before insert to avoid
|
|
175
|
+
// transient uniqueness failures during retries/rotation edge-cases.
|
|
176
|
+
await this.Tokens.destroy({
|
|
177
|
+
where: {
|
|
178
|
+
[Op.or]: [{ access: normalized.accessToken ?? '' }, { refresh: normalized.refreshToken }]
|
|
179
|
+
}
|
|
180
|
+
});
|
|
184
181
|
await this.Tokens.create({
|
|
185
182
|
user_id: resolvedUserId,
|
|
186
183
|
real_user_id: resolvedRealUserId,
|
|
@@ -340,10 +337,7 @@ export class SequelizeTokenStore extends TokenStore {
|
|
|
340
337
|
return;
|
|
341
338
|
}
|
|
342
339
|
normalizeUserId(identifier) {
|
|
343
|
-
|
|
344
|
-
throw new Error(`Unable to normalise user identifier: ${identifier}`);
|
|
345
|
-
}
|
|
346
|
-
return String(identifier);
|
|
340
|
+
return normalizeStringUserId(identifier);
|
|
347
341
|
}
|
|
348
342
|
resolveRealUserId(ruid) {
|
|
349
343
|
if (ruid === undefined || ruid === null) {
|
|
@@ -9,7 +9,14 @@ export interface Token {
|
|
|
9
9
|
status?: TokenStatus;
|
|
10
10
|
ruid?: string;
|
|
11
11
|
clientId?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Optional session partition key. Token stores may use `domain` and `fingerprint`
|
|
14
|
+
* to replace previous sessions that match the same bucket.
|
|
15
|
+
*/
|
|
12
16
|
domain?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Optional device/session fingerprint used together with `domain` for session bucketing.
|
|
19
|
+
*/
|
|
13
20
|
fingerprint?: string;
|
|
14
21
|
label?: string;
|
|
15
22
|
browser?: string;
|
package/dist/esm/user/memory.js
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
|
+
import { normalizeNumericUserId } from '../auth-api/user-id.js';
|
|
1
2
|
import { UserStore } from './base.js';
|
|
2
3
|
function cloneUser(user) {
|
|
3
4
|
return { ...user };
|
|
4
5
|
}
|
|
5
|
-
|
|
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
|
-
throw new Error(`Unable to normalise user identifier: ${identifier}`);
|
|
13
|
-
}
|
|
6
|
+
const normalizeUserId = normalizeNumericUserId;
|
|
14
7
|
export class MemoryUserStore extends UserStore {
|
|
15
8
|
constructor(options = {}) {
|
|
16
9
|
super({
|
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
import { DataTypes, Model, Op } from 'sequelize';
|
|
2
|
+
import { normalizeNumericUserId } from '../auth-api/user-id.js';
|
|
3
|
+
import { DIALECTS_SUPPORTING_UNSIGNED, applyTablePrefix } from '../sequelize-utils.js';
|
|
2
4
|
import { UserStore } from './base.js';
|
|
3
|
-
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
4
|
-
function normalizeTablePrefix(prefix) {
|
|
5
|
-
if (!prefix) {
|
|
6
|
-
return undefined;
|
|
7
|
-
}
|
|
8
|
-
const trimmed = prefix.trim();
|
|
9
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
10
|
-
}
|
|
11
|
-
function applyTablePrefix(prefix, tableName) {
|
|
12
|
-
const normalized = normalizeTablePrefix(prefix);
|
|
13
|
-
return normalized ? `${normalized}${tableName}` : tableName;
|
|
14
|
-
}
|
|
15
5
|
function integerIdType(sequelize) {
|
|
16
6
|
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? DataTypes.INTEGER.UNSIGNED : DataTypes.INTEGER;
|
|
17
7
|
}
|
|
@@ -178,12 +168,6 @@ export class SequelizeUserStore extends UserStore {
|
|
|
178
168
|
};
|
|
179
169
|
}
|
|
180
170
|
normalizeUserId(identifier) {
|
|
181
|
-
|
|
182
|
-
return identifier;
|
|
183
|
-
}
|
|
184
|
-
if (typeof identifier === 'string' && /^\d+$/.test(identifier)) {
|
|
185
|
-
return Number(identifier);
|
|
186
|
-
}
|
|
187
|
-
throw new Error(`Unable to normalise user identifier: ${identifier}`);
|
|
171
|
+
return normalizeNumericUserId(identifier);
|
|
188
172
|
}
|
|
189
173
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"openapi": "3.1.0",
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "API Server Base",
|
|
5
|
-
"version": "2.0.0-beta.
|
|
5
|
+
"version": "2.0.0-beta.19",
|
|
6
6
|
"description": "OpenAPI reference for ApiServer base endpoints and optional modules. Auth, passkey, and oauth modules are optional and require the corresponding module to be enabled in the ApiServer config. Base endpoints are always available."
|
|
7
7
|
},
|
|
8
8
|
"servers": [
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"get": {
|
|
35
35
|
"tags": ["base"],
|
|
36
36
|
"summary": "Health check",
|
|
37
|
-
"description": "Auth: none. Returns server health and version metadata.",
|
|
37
|
+
"description": "Auth: none. Returns ApiResponse<PingResponseData> with server health and version metadata.",
|
|
38
38
|
"security": [],
|
|
39
39
|
"responses": {
|
|
40
40
|
"200": {
|
|
@@ -760,145 +760,6 @@
|
|
|
760
760
|
}
|
|
761
761
|
}
|
|
762
762
|
}
|
|
763
|
-
},
|
|
764
|
-
"delete": {
|
|
765
|
-
"tags": ["passkey"],
|
|
766
|
-
"summary": "Delete a passkey credential",
|
|
767
|
-
"description": "Auth: strict. Deletes a passkey credential by id provided in the request body.",
|
|
768
|
-
"security": [{ "accessTokenBearer": [] }, { "accessTokenCookie": [] }, { "apiKeyBearer": [] }],
|
|
769
|
-
"requestBody": {
|
|
770
|
-
"required": true,
|
|
771
|
-
"content": {
|
|
772
|
-
"application/json": {
|
|
773
|
-
"schema": {
|
|
774
|
-
"type": "object",
|
|
775
|
-
"properties": {
|
|
776
|
-
"credentialId": {
|
|
777
|
-
"type": "string",
|
|
778
|
-
"description": "Credential id (base64url)."
|
|
779
|
-
}
|
|
780
|
-
},
|
|
781
|
-
"required": ["credentialId"]
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
},
|
|
786
|
-
"responses": {
|
|
787
|
-
"200": {
|
|
788
|
-
"description": "Credential deleted.",
|
|
789
|
-
"content": {
|
|
790
|
-
"application/json": {
|
|
791
|
-
"schema": {
|
|
792
|
-
"allOf": [
|
|
793
|
-
{ "$ref": "#/components/schemas/ApiResponse" },
|
|
794
|
-
{
|
|
795
|
-
"type": "object",
|
|
796
|
-
"properties": {
|
|
797
|
-
"data": { "$ref": "#/components/schemas/PasskeyDeleteResponseData" }
|
|
798
|
-
},
|
|
799
|
-
"required": ["data"]
|
|
800
|
-
}
|
|
801
|
-
]
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
},
|
|
806
|
-
"400": {
|
|
807
|
-
"description": "Missing or invalid credentialId.",
|
|
808
|
-
"content": {
|
|
809
|
-
"application/json": {
|
|
810
|
-
"schema": {
|
|
811
|
-
"allOf": [
|
|
812
|
-
{ "$ref": "#/components/schemas/ApiResponse" },
|
|
813
|
-
{
|
|
814
|
-
"type": "object",
|
|
815
|
-
"properties": {
|
|
816
|
-
"data": { "type": "null" }
|
|
817
|
-
},
|
|
818
|
-
"required": ["data"]
|
|
819
|
-
}
|
|
820
|
-
]
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
},
|
|
825
|
-
"401": {
|
|
826
|
-
"description": "Authentication required.",
|
|
827
|
-
"content": {
|
|
828
|
-
"application/json": {
|
|
829
|
-
"schema": {
|
|
830
|
-
"allOf": [
|
|
831
|
-
{ "$ref": "#/components/schemas/ApiResponse" },
|
|
832
|
-
{
|
|
833
|
-
"type": "object",
|
|
834
|
-
"properties": {
|
|
835
|
-
"data": { "type": "null" }
|
|
836
|
-
},
|
|
837
|
-
"required": ["data"]
|
|
838
|
-
}
|
|
839
|
-
]
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
},
|
|
844
|
-
"403": {
|
|
845
|
-
"description": "Authenticated user not found.",
|
|
846
|
-
"content": {
|
|
847
|
-
"application/json": {
|
|
848
|
-
"schema": {
|
|
849
|
-
"allOf": [
|
|
850
|
-
{ "$ref": "#/components/schemas/ApiResponse" },
|
|
851
|
-
{
|
|
852
|
-
"type": "object",
|
|
853
|
-
"properties": {
|
|
854
|
-
"data": { "type": "null" }
|
|
855
|
-
},
|
|
856
|
-
"required": ["data"]
|
|
857
|
-
}
|
|
858
|
-
]
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
},
|
|
863
|
-
"404": {
|
|
864
|
-
"description": "Passkey not found.",
|
|
865
|
-
"content": {
|
|
866
|
-
"application/json": {
|
|
867
|
-
"schema": {
|
|
868
|
-
"allOf": [
|
|
869
|
-
{ "$ref": "#/components/schemas/ApiResponse" },
|
|
870
|
-
{
|
|
871
|
-
"type": "object",
|
|
872
|
-
"properties": {
|
|
873
|
-
"data": { "type": "null" }
|
|
874
|
-
},
|
|
875
|
-
"required": ["data"]
|
|
876
|
-
}
|
|
877
|
-
]
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
},
|
|
882
|
-
"501": {
|
|
883
|
-
"description": "Passkey management not configured.",
|
|
884
|
-
"content": {
|
|
885
|
-
"application/json": {
|
|
886
|
-
"schema": {
|
|
887
|
-
"allOf": [
|
|
888
|
-
{ "$ref": "#/components/schemas/ApiResponse" },
|
|
889
|
-
{
|
|
890
|
-
"type": "object",
|
|
891
|
-
"properties": {
|
|
892
|
-
"data": { "type": "null" }
|
|
893
|
-
},
|
|
894
|
-
"required": ["data"]
|
|
895
|
-
}
|
|
896
|
-
]
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
763
|
}
|
|
903
764
|
},
|
|
904
765
|
"/api/auth/v1/passkeys/{credentialId}": {
|
|
@@ -1431,7 +1292,7 @@
|
|
|
1431
1292
|
},
|
|
1432
1293
|
"ApiResponse": {
|
|
1433
1294
|
"type": "object",
|
|
1434
|
-
"description": "
|
|
1295
|
+
"description": "ApiResponse<T> standard envelope used by all endpoints (success or error). T is the endpoint-specific payload type carried in the data field. Mirrors ApiClientBase.ApiResponseData.",
|
|
1435
1296
|
"properties": {
|
|
1436
1297
|
"success": {
|
|
1437
1298
|
"type": "boolean",
|
|
@@ -1463,6 +1324,10 @@
|
|
|
1463
1324
|
"type": "object",
|
|
1464
1325
|
"description": "Data payload returned by GET /api/v1/ping.",
|
|
1465
1326
|
"properties": {
|
|
1327
|
+
"success": {
|
|
1328
|
+
"type": "boolean",
|
|
1329
|
+
"description": "Always true (redundant with ApiResponse.success)."
|
|
1330
|
+
},
|
|
1466
1331
|
"status": {
|
|
1467
1332
|
"type": "string",
|
|
1468
1333
|
"description": "Health indicator (\"ok\")."
|
|
@@ -1477,15 +1342,16 @@
|
|
|
1477
1342
|
"type": "number"
|
|
1478
1343
|
},
|
|
1479
1344
|
"startedAt": {
|
|
1480
|
-
"type": "
|
|
1481
|
-
"format": "
|
|
1345
|
+
"type": "integer",
|
|
1346
|
+
"format": "int64",
|
|
1347
|
+
"description": "Server start time as milliseconds since epoch."
|
|
1482
1348
|
},
|
|
1483
1349
|
"timestamp": {
|
|
1484
1350
|
"type": "string",
|
|
1485
1351
|
"format": "date-time"
|
|
1486
1352
|
}
|
|
1487
1353
|
},
|
|
1488
|
-
"required": ["status", "uptimeSec", "startedAt", "timestamp"]
|
|
1354
|
+
"required": ["success", "status", "uptimeSec", "startedAt", "timestamp"]
|
|
1489
1355
|
},
|
|
1490
1356
|
"AuthTokensResponseData": {
|
|
1491
1357
|
"type": "object",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@technomoron/api-server-base",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.19",
|
|
4
4
|
"description": "Api Server Skeleton / Base Class",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.cjs",
|
|
@@ -70,34 +70,34 @@
|
|
|
70
70
|
"@simplewebauthn/server": "^13.2.2",
|
|
71
71
|
"@types/cookie-parser": "^1.4.10",
|
|
72
72
|
"@types/cors": "^2.8.19",
|
|
73
|
-
"@types/express": "^
|
|
73
|
+
"@types/express": "^5.0.6",
|
|
74
74
|
"@types/jsonwebtoken": "^9.0.10",
|
|
75
|
-
"@types/multer": "^
|
|
76
|
-
"bcryptjs": "^
|
|
75
|
+
"@types/multer": "^2.0.0",
|
|
76
|
+
"bcryptjs": "^3.0.3",
|
|
77
77
|
"cookie-parser": "^1.4.7",
|
|
78
|
-
"cors": "^2.8.
|
|
79
|
-
"express": "^
|
|
78
|
+
"cors": "^2.8.6",
|
|
79
|
+
"express": "^5.2.1",
|
|
80
80
|
"jsonwebtoken": "^9.0.3",
|
|
81
81
|
"multer": "^2.0.2"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@types/
|
|
85
|
-
"@types/express-serve-static-core": "^5.1.0",
|
|
84
|
+
"@types/express-serve-static-core": "^5.1.1",
|
|
86
85
|
"@types/supertest": "^6.0.3",
|
|
87
86
|
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
88
87
|
"@typescript-eslint/parser": "^8.54.0",
|
|
88
|
+
"@vitest/coverage-v8": "4.0.18",
|
|
89
89
|
"eslint": "^9.39.2",
|
|
90
90
|
"eslint-config-prettier": "^10.1.8",
|
|
91
91
|
"eslint-plugin-import": "^2.32.0",
|
|
92
92
|
"jsonc-eslint-parser": "^2.4.2",
|
|
93
|
-
"mysql2": "^3.16.
|
|
94
|
-
"pg": "^8.
|
|
93
|
+
"mysql2": "^3.16.3",
|
|
94
|
+
"pg": "^8.18.0",
|
|
95
95
|
"prettier": "^3.8.1",
|
|
96
96
|
"sequelize": "^6.37.7",
|
|
97
97
|
"sqlite3": "^5.1.7",
|
|
98
|
-
"supertest": "^7.
|
|
98
|
+
"supertest": "^7.2.2",
|
|
99
99
|
"typescript": "^5.9.3",
|
|
100
|
-
"vitest": "^4.0.
|
|
100
|
+
"vitest": "^4.0.18"
|
|
101
101
|
},
|
|
102
102
|
"peerDependencies": {
|
|
103
103
|
"mysql2": "^3.16.0",
|