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/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
- `${config.dauthBasePath}/app/${config.domainName}/logout`,
313
- {
314
- method: 'POST',
315
- headers: { 'Content-Type': 'application/json' },
316
- body: JSON.stringify({
317
- refreshToken: session.refreshToken,
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
- '/profile-redirect',
389
- async (req: Request, res: Response) => {
390
- const config = await getConfig();
391
- if (!verifyCsrf(req, config.csrfCookieName)) {
392
- return res.status(403).send({
393
- status: 'csrf-invalid',
394
- message: 'CSRF token invalid',
395
- });
396
- }
397
- const session = readSession(req, config);
398
- if (!session) {
399
- return res.status(401).send({
400
- status: 'no-session',
401
- message: 'Not authenticated',
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
- const response = await fetch(
411
- `${config.dauthBasePath}/app/${config.domainName}/profile-code`,
412
- {
413
- method: 'POST',
414
- headers: {
415
- 'Content-Type': 'application/json',
416
- Authorization: refreshed.accessToken,
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
- const data = (await response.json()) as { code: string };
427
-
428
- // Build redirect URL to dauth frontend
429
- const dauthFrontendUrl = opts.dauthUrl
430
- ? opts.dauthUrl.replace(/\/+$/, '')
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":[]}