dauth-md-node 4.0.0 → 4.1.0
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.md +161 -134
- package/dist/{chunk-4A7BR4EM.mjs → chunk-RKH7YKIR.mjs} +1 -1
- package/dist/chunk-RKH7YKIR.mjs.map +1 -0
- package/dist/index.d.mts +37 -2
- package/dist/index.d.ts +37 -2
- package/dist/index.js +56 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +53 -15
- package/dist/index.mjs.map +1 -1
- package/dist/router.js +47 -55
- package/dist/router.js.map +1 -1
- package/dist/router.mjs +48 -56
- package/dist/router.mjs.map +1 -1
- package/package.json +15 -1
- package/src/api/dauth.api.ts +80 -0
- package/src/csrf.ts +7 -8
- package/src/index.ts +34 -52
- package/src/router.ts +55 -71
- package/src/session.ts +1 -4
- package/dist/chunk-4A7BR4EM.mjs.map +0 -1
package/src/router.ts
CHANGED
|
@@ -45,11 +45,9 @@ function clearStaleLocks(): void {
|
|
|
45
45
|
async function resolveConfig(
|
|
46
46
|
opts: DauthRouterOptions
|
|
47
47
|
): Promise<ResolvedConfig> {
|
|
48
|
-
const secure =
|
|
49
|
-
opts.secure ?? process.env.NODE_ENV !== 'development';
|
|
48
|
+
const secure = opts.secure ?? process.env.NODE_ENV !== 'development';
|
|
50
49
|
const cookieName =
|
|
51
|
-
opts.cookieName ??
|
|
52
|
-
(secure ? '__Host-dauth-session' : 'dauth-session');
|
|
50
|
+
opts.cookieName ?? (secure ? '__Host-dauth-session' : 'dauth-session');
|
|
53
51
|
const csrfCookieName =
|
|
54
52
|
opts.csrfCookieName ?? (secure ? '__Host-csrf' : 'csrf-token');
|
|
55
53
|
const maxAgeMs = (opts.maxAge ?? 30 * 24 * 3600) * 1000;
|
|
@@ -57,9 +55,7 @@ async function resolveConfig(
|
|
|
57
55
|
const keys: Buffer[] = [];
|
|
58
56
|
keys.push(await deriveEncryptionKey(opts.tsk, opts.sessionSalt));
|
|
59
57
|
if (opts.previousTsk) {
|
|
60
|
-
keys.push(
|
|
61
|
-
await deriveEncryptionKey(opts.previousTsk, opts.sessionSalt)
|
|
62
|
-
);
|
|
58
|
+
keys.push(await deriveEncryptionKey(opts.previousTsk, opts.sessionSalt));
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
let dauthBasePath: string;
|
|
@@ -308,21 +304,16 @@ export function dauthRouter(opts: DauthRouterOptions): Router {
|
|
|
308
304
|
const session = readSession(req, config);
|
|
309
305
|
if (session) {
|
|
310
306
|
// Revoke refresh token server-to-server (fire-and-forget)
|
|
311
|
-
fetch(
|
|
312
|
-
|
|
313
|
-
{
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}),
|
|
319
|
-
}
|
|
320
|
-
).catch(() => {});
|
|
307
|
+
fetch(`${config.dauthBasePath}/app/${config.domainName}/logout`, {
|
|
308
|
+
method: 'POST',
|
|
309
|
+
headers: { 'Content-Type': 'application/json' },
|
|
310
|
+
body: JSON.stringify({
|
|
311
|
+
refreshToken: session.refreshToken,
|
|
312
|
+
}),
|
|
313
|
+
}).catch(() => {});
|
|
321
314
|
}
|
|
322
315
|
clearCookies(res, config);
|
|
323
|
-
return res
|
|
324
|
-
.status(200)
|
|
325
|
-
.send({ status: 'success', message: 'Logged out' });
|
|
316
|
+
return res.status(200).send({ status: 'success', message: 'Logged out' });
|
|
326
317
|
});
|
|
327
318
|
|
|
328
319
|
// PATCH /user — CSRF required
|
|
@@ -384,61 +375,54 @@ export function dauthRouter(opts: DauthRouterOptions): Router {
|
|
|
384
375
|
});
|
|
385
376
|
|
|
386
377
|
// GET /profile-redirect — CSRF required (generates profile code)
|
|
387
|
-
router.get(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
const refreshed = await maybeRefreshTokens(
|
|
405
|
-
session,
|
|
406
|
-
config,
|
|
407
|
-
res
|
|
408
|
-
);
|
|
378
|
+
router.get('/profile-redirect', async (req: Request, res: Response) => {
|
|
379
|
+
const config = await getConfig();
|
|
380
|
+
if (!verifyCsrf(req, config.csrfCookieName)) {
|
|
381
|
+
return res.status(403).send({
|
|
382
|
+
status: 'csrf-invalid',
|
|
383
|
+
message: 'CSRF token invalid',
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
const session = readSession(req, config);
|
|
387
|
+
if (!session) {
|
|
388
|
+
return res.status(401).send({
|
|
389
|
+
status: 'no-session',
|
|
390
|
+
message: 'Not authenticated',
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
const refreshed = await maybeRefreshTokens(session, config, res);
|
|
409
394
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
);
|
|
420
|
-
if (!response.ok) {
|
|
421
|
-
return res.status(response.status).send({
|
|
422
|
-
status: 'profile-code-error',
|
|
423
|
-
message: 'Could not generate profile code',
|
|
424
|
-
});
|
|
395
|
+
const response = await fetch(
|
|
396
|
+
`${config.dauthBasePath}/app/${config.domainName}/profile-code`,
|
|
397
|
+
{
|
|
398
|
+
method: 'POST',
|
|
399
|
+
headers: {
|
|
400
|
+
'Content-Type': 'application/json',
|
|
401
|
+
Authorization: refreshed.accessToken,
|
|
402
|
+
},
|
|
425
403
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
: process.env.DAUTH_URL
|
|
432
|
-
? process.env.DAUTH_URL.replace(/\/+$/, '')
|
|
433
|
-
: process.env.NODE_ENV === 'development'
|
|
434
|
-
? 'http://localhost:5185'
|
|
435
|
-
: 'https://dauth.ovh';
|
|
436
|
-
|
|
437
|
-
return res.status(200).send({
|
|
438
|
-
redirectUrl: `${dauthFrontendUrl}/${config.domainName}/update-user?code=${data.code}`,
|
|
404
|
+
);
|
|
405
|
+
if (!response.ok) {
|
|
406
|
+
return res.status(response.status).send({
|
|
407
|
+
status: 'profile-code-error',
|
|
408
|
+
message: 'Could not generate profile code',
|
|
439
409
|
});
|
|
440
410
|
}
|
|
441
|
-
|
|
411
|
+
const data = (await response.json()) as { code: string };
|
|
412
|
+
|
|
413
|
+
// Build redirect URL to dauth frontend
|
|
414
|
+
const dauthFrontendUrl = opts.dauthUrl
|
|
415
|
+
? opts.dauthUrl.replace(/\/+$/, '')
|
|
416
|
+
: process.env.DAUTH_URL
|
|
417
|
+
? process.env.DAUTH_URL.replace(/\/+$/, '')
|
|
418
|
+
: process.env.NODE_ENV === 'development'
|
|
419
|
+
? 'http://localhost:5185'
|
|
420
|
+
: 'https://dauth.ovh';
|
|
421
|
+
|
|
422
|
+
return res.status(200).send({
|
|
423
|
+
redirectUrl: `${dauthFrontendUrl}/${config.domainName}/update-user?code=${data.code}`,
|
|
424
|
+
});
|
|
425
|
+
});
|
|
442
426
|
|
|
443
427
|
return router;
|
|
444
428
|
}
|
package/src/session.ts
CHANGED
|
@@ -31,10 +31,7 @@ export async function deriveEncryptionKey(
|
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export function encryptSession(
|
|
35
|
-
payload: SessionPayload,
|
|
36
|
-
key: Buffer
|
|
37
|
-
): string {
|
|
34
|
+
export function encryptSession(payload: SessionPayload, key: Buffer): string {
|
|
38
35
|
const nonce = crypto.randomBytes(12);
|
|
39
36
|
const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce);
|
|
40
37
|
const plaintext = JSON.stringify(payload);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/api/utils/config.ts","../src/session.ts"],"sourcesContent":["export const apiVersion = 'v1';\nexport const serverDomain = 'dauth.ovh';\n\nexport function getServerBasePath(): string {\n if (process.env.DAUTH_URL) {\n const base = process.env.DAUTH_URL.replace(/\\/+$/, '');\n return `${base}/api/${apiVersion}`;\n }\n\n const isLocalhost = process.env.NODE_ENV === 'development';\n const serverPort = 4012;\n const serverLocalUrl = `http://localhost:${serverPort}/api/${apiVersion}`;\n const serverProdUrl = `https://${serverDomain}/api/${apiVersion}`;\n return isLocalhost ? serverLocalUrl : serverProdUrl;\n}\n","import crypto from 'crypto';\n\nexport interface SessionPayload {\n accessToken: string;\n refreshToken: string;\n}\n\nconst INFO = 'dauth-cookie-enc-v1';\nconst DEFAULT_SALT = Buffer.from(\n 'a3f8c1d7e9b24f6081c5d3a7e2f49b0653d81f7a2e94c0b6d8f3a5e1c7b09d42',\n 'hex'\n);\n\nexport async function deriveEncryptionKey(\n tsk: string,\n salt?: string\n): Promise<Buffer> {\n const saltBuf = salt ? Buffer.from(salt, 'hex') : DEFAULT_SALT;\n return new Promise((resolve, reject) => {\n crypto.hkdf(\n 'sha256',\n Buffer.from(tsk),\n saltBuf,\n INFO,\n 32,\n (err, derivedKey) => {\n if (err) return reject(err);\n resolve(Buffer.from(derivedKey));\n }\n );\n });\n}\n\nexport function encryptSession(\n payload: SessionPayload,\n key: Buffer\n): string {\n const nonce = crypto.randomBytes(12);\n const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce);\n const plaintext = JSON.stringify(payload);\n const encrypted = Buffer.concat([\n cipher.update(plaintext, 'utf8'),\n cipher.final(),\n ]);\n const authTag = cipher.getAuthTag();\n // Format: base64(nonce + ciphertext + authTag)\n return Buffer.concat([nonce, encrypted, authTag]).toString('base64');\n}\n\nexport function decryptSession(\n ciphertext: string,\n key: Buffer\n): SessionPayload | null {\n try {\n const buf = Buffer.from(ciphertext, 'base64');\n if (buf.length < 12 + 16) return null; // nonce(12) + authTag(16) minimum\n const nonce = buf.subarray(0, 12);\n const authTag = buf.subarray(buf.length - 16);\n const encrypted = buf.subarray(12, buf.length - 16);\n const decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);\n decipher.setAuthTag(authTag);\n const decrypted = Buffer.concat([\n decipher.update(encrypted),\n decipher.final(),\n ]);\n return JSON.parse(decrypted.toString('utf8')) as SessionPayload;\n } catch {\n return null;\n }\n}\n\nexport function decryptSessionWithKeys(\n ciphertext: string,\n keys: Buffer[]\n): SessionPayload | null {\n for (const key of keys) {\n const result = decryptSession(ciphertext, key);\n if (result) return result;\n }\n return null;\n}\n"],"mappings":";AAAO,IAAM,aAAa;AACnB,IAAM,eAAe;AAErB,SAAS,oBAA4B;AAC1C,MAAI,QAAQ,IAAI,WAAW;AACzB,UAAM,OAAO,QAAQ,IAAI,UAAU,QAAQ,QAAQ,EAAE;AACrD,WAAO,GAAG,IAAI,QAAQ,UAAU;AAAA,EAClC;AAEA,QAAM,cAAc,QAAQ,IAAI,aAAa;AAC7C,QAAM,aAAa;AACnB,QAAM,iBAAiB,oBAAoB,UAAU,QAAQ,UAAU;AACvE,QAAM,gBAAgB,WAAW,YAAY,QAAQ,UAAU;AAC/D,SAAO,cAAc,iBAAiB;AACxC;;;ACdA,OAAO,YAAY;AAOnB,IAAM,OAAO;AACb,IAAM,eAAe,OAAO;AAAA,EAC1B;AAAA,EACA;AACF;AAEA,eAAsB,oBACpB,KACA,MACiB;AACjB,QAAM,UAAU,OAAO,OAAO,KAAK,MAAM,KAAK,IAAI;AAClD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAO;AAAA,MACL;AAAA,MACA,OAAO,KAAK,GAAG;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA,CAAC,KAAK,eAAe;AACnB,YAAI,IAAK,QAAO,OAAO,GAAG;AAC1B,gBAAQ,OAAO,KAAK,UAAU,CAAC;AAAA,MACjC;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEO,SAAS,eACd,SACA,KACQ;AACR,QAAM,QAAQ,OAAO,YAAY,EAAE;AACnC,QAAM,SAAS,OAAO,eAAe,eAAe,KAAK,KAAK;AAC9D,QAAM,YAAY,KAAK,UAAU,OAAO;AACxC,QAAM,YAAY,OAAO,OAAO;AAAA,IAC9B,OAAO,OAAO,WAAW,MAAM;AAAA,IAC/B,OAAO,MAAM;AAAA,EACf,CAAC;AACD,QAAM,UAAU,OAAO,WAAW;AAElC,SAAO,OAAO,OAAO,CAAC,OAAO,WAAW,OAAO,CAAC,EAAE,SAAS,QAAQ;AACrE;AAEO,SAAS,eACd,YACA,KACuB;AACvB,MAAI;AACF,UAAM,MAAM,OAAO,KAAK,YAAY,QAAQ;AAC5C,QAAI,IAAI,SAAS,KAAK,GAAI,QAAO;AACjC,UAAM,QAAQ,IAAI,SAAS,GAAG,EAAE;AAChC,UAAM,UAAU,IAAI,SAAS,IAAI,SAAS,EAAE;AAC5C,UAAM,YAAY,IAAI,SAAS,IAAI,IAAI,SAAS,EAAE;AAClD,UAAM,WAAW,OAAO,iBAAiB,eAAe,KAAK,KAAK;AAClE,aAAS,WAAW,OAAO;AAC3B,UAAM,YAAY,OAAO,OAAO;AAAA,MAC9B,SAAS,OAAO,SAAS;AAAA,MACzB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO,KAAK,MAAM,UAAU,SAAS,MAAM,CAAC;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,uBACd,YACA,MACuB;AACvB,aAAW,OAAO,MAAM;AACtB,UAAM,SAAS,eAAe,YAAY,GAAG;AAC7C,QAAI,OAAQ,QAAO;AAAA,EACrB;AACA,SAAO;AACT;","names":[]}
|