dauth-md-node 3.0.2 → 4.0.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/dist/chunk-4A7BR4EM.mjs +82 -0
- package/dist/chunk-4A7BR4EM.mjs.map +1 -0
- package/dist/index.d.mts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +108 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +65 -19
- package/dist/index.mjs.map +1 -1
- package/dist/router.d.mts +16 -0
- package/dist/router.d.ts +16 -0
- package/dist/router.js +436 -0
- package/dist/router.js.map +1 -0
- package/dist/router.mjs +333 -0
- package/dist/router.mjs.map +1 -0
- package/package.json +22 -2
- package/src/csrf.ts +18 -0
- package/src/index.ts +100 -15
- package/src/router.ts +444 -0
- package/src/session.ts +81 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// src/api/utils/config.ts
|
|
2
|
+
var apiVersion = "v1";
|
|
3
|
+
var serverDomain = "dauth.ovh";
|
|
4
|
+
function getServerBasePath() {
|
|
5
|
+
if (process.env.DAUTH_URL) {
|
|
6
|
+
const base = process.env.DAUTH_URL.replace(/\/+$/, "");
|
|
7
|
+
return `${base}/api/${apiVersion}`;
|
|
8
|
+
}
|
|
9
|
+
const isLocalhost = process.env.NODE_ENV === "development";
|
|
10
|
+
const serverPort = 4012;
|
|
11
|
+
const serverLocalUrl = `http://localhost:${serverPort}/api/${apiVersion}`;
|
|
12
|
+
const serverProdUrl = `https://${serverDomain}/api/${apiVersion}`;
|
|
13
|
+
return isLocalhost ? serverLocalUrl : serverProdUrl;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/session.ts
|
|
17
|
+
import crypto from "crypto";
|
|
18
|
+
var INFO = "dauth-cookie-enc-v1";
|
|
19
|
+
var DEFAULT_SALT = Buffer.from(
|
|
20
|
+
"a3f8c1d7e9b24f6081c5d3a7e2f49b0653d81f7a2e94c0b6d8f3a5e1c7b09d42",
|
|
21
|
+
"hex"
|
|
22
|
+
);
|
|
23
|
+
async function deriveEncryptionKey(tsk, salt) {
|
|
24
|
+
const saltBuf = salt ? Buffer.from(salt, "hex") : DEFAULT_SALT;
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
crypto.hkdf(
|
|
27
|
+
"sha256",
|
|
28
|
+
Buffer.from(tsk),
|
|
29
|
+
saltBuf,
|
|
30
|
+
INFO,
|
|
31
|
+
32,
|
|
32
|
+
(err, derivedKey) => {
|
|
33
|
+
if (err) return reject(err);
|
|
34
|
+
resolve(Buffer.from(derivedKey));
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function encryptSession(payload, key) {
|
|
40
|
+
const nonce = crypto.randomBytes(12);
|
|
41
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", key, nonce);
|
|
42
|
+
const plaintext = JSON.stringify(payload);
|
|
43
|
+
const encrypted = Buffer.concat([
|
|
44
|
+
cipher.update(plaintext, "utf8"),
|
|
45
|
+
cipher.final()
|
|
46
|
+
]);
|
|
47
|
+
const authTag = cipher.getAuthTag();
|
|
48
|
+
return Buffer.concat([nonce, encrypted, authTag]).toString("base64");
|
|
49
|
+
}
|
|
50
|
+
function decryptSession(ciphertext, key) {
|
|
51
|
+
try {
|
|
52
|
+
const buf = Buffer.from(ciphertext, "base64");
|
|
53
|
+
if (buf.length < 12 + 16) return null;
|
|
54
|
+
const nonce = buf.subarray(0, 12);
|
|
55
|
+
const authTag = buf.subarray(buf.length - 16);
|
|
56
|
+
const encrypted = buf.subarray(12, buf.length - 16);
|
|
57
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", key, nonce);
|
|
58
|
+
decipher.setAuthTag(authTag);
|
|
59
|
+
const decrypted = Buffer.concat([
|
|
60
|
+
decipher.update(encrypted),
|
|
61
|
+
decipher.final()
|
|
62
|
+
]);
|
|
63
|
+
return JSON.parse(decrypted.toString("utf8"));
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function decryptSessionWithKeys(ciphertext, keys) {
|
|
69
|
+
for (const key of keys) {
|
|
70
|
+
const result = decryptSession(ciphertext, key);
|
|
71
|
+
if (result) return result;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
getServerBasePath,
|
|
78
|
+
deriveEncryptionKey,
|
|
79
|
+
encryptSession,
|
|
80
|
+
decryptSessionWithKeys
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=chunk-4A7BR4EM.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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":[]}
|
package/dist/index.d.mts
CHANGED
|
@@ -51,16 +51,23 @@ interface IRequestDauth extends Request {
|
|
|
51
51
|
authorization: string;
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
interface SessionOptions {
|
|
55
|
+
cookieName?: string;
|
|
56
|
+
secure?: boolean;
|
|
57
|
+
previousTsk?: string;
|
|
58
|
+
sessionSalt?: string;
|
|
59
|
+
}
|
|
54
60
|
interface DauthOptions {
|
|
55
61
|
domainName: string;
|
|
56
62
|
tsk: string;
|
|
57
63
|
cache?: CacheOptions;
|
|
64
|
+
session?: SessionOptions;
|
|
58
65
|
}
|
|
59
66
|
interface TCustomResponse extends Response {
|
|
60
67
|
status(code: number): this;
|
|
61
68
|
send(body?: unknown): this;
|
|
62
69
|
}
|
|
63
70
|
|
|
64
|
-
declare const dauth: ({ domainName, tsk, cache }: DauthOptions) => (req: IRequestDauth, res: TCustomResponse, next: NextFunction) => Promise<void | TCustomResponse>;
|
|
71
|
+
declare const dauth: ({ domainName, tsk, cache, session, }: DauthOptions) => (req: IRequestDauth, res: TCustomResponse, next: NextFunction) => Promise<void | TCustomResponse>;
|
|
65
72
|
|
|
66
|
-
export { type AuthMethodType, type CacheOptions, type DauthOptions, type IDauthUser, type IRequestDauth, UserCache, dauth };
|
|
73
|
+
export { type AuthMethodType, type CacheOptions, type DauthOptions, type IDauthUser, type IRequestDauth, type SessionOptions, UserCache, dauth };
|
package/dist/index.d.ts
CHANGED
|
@@ -51,16 +51,23 @@ interface IRequestDauth extends Request {
|
|
|
51
51
|
authorization: string;
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
interface SessionOptions {
|
|
55
|
+
cookieName?: string;
|
|
56
|
+
secure?: boolean;
|
|
57
|
+
previousTsk?: string;
|
|
58
|
+
sessionSalt?: string;
|
|
59
|
+
}
|
|
54
60
|
interface DauthOptions {
|
|
55
61
|
domainName: string;
|
|
56
62
|
tsk: string;
|
|
57
63
|
cache?: CacheOptions;
|
|
64
|
+
session?: SessionOptions;
|
|
58
65
|
}
|
|
59
66
|
interface TCustomResponse extends Response {
|
|
60
67
|
status(code: number): this;
|
|
61
68
|
send(body?: unknown): this;
|
|
62
69
|
}
|
|
63
70
|
|
|
64
|
-
declare const dauth: ({ domainName, tsk, cache }: DauthOptions) => (req: IRequestDauth, res: TCustomResponse, next: NextFunction) => Promise<void | TCustomResponse>;
|
|
71
|
+
declare const dauth: ({ domainName, tsk, cache, session, }: DauthOptions) => (req: IRequestDauth, res: TCustomResponse, next: NextFunction) => Promise<void | TCustomResponse>;
|
|
65
72
|
|
|
66
|
-
export { type AuthMethodType, type CacheOptions, type DauthOptions, type IDauthUser, type IRequestDauth, UserCache, dauth };
|
|
73
|
+
export { type AuthMethodType, type CacheOptions, type DauthOptions, type IDauthUser, type IRequestDauth, type SessionOptions, UserCache, dauth };
|
package/dist/index.js
CHANGED
|
@@ -102,14 +102,118 @@ var UserCache = class {
|
|
|
102
102
|
}
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
+
// src/session.ts
|
|
106
|
+
var import_crypto = __toESM(require("crypto"));
|
|
107
|
+
var INFO = "dauth-cookie-enc-v1";
|
|
108
|
+
var DEFAULT_SALT = Buffer.from(
|
|
109
|
+
"a3f8c1d7e9b24f6081c5d3a7e2f49b0653d81f7a2e94c0b6d8f3a5e1c7b09d42",
|
|
110
|
+
"hex"
|
|
111
|
+
);
|
|
112
|
+
async function deriveEncryptionKey(tsk, salt) {
|
|
113
|
+
const saltBuf = salt ? Buffer.from(salt, "hex") : DEFAULT_SALT;
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
import_crypto.default.hkdf(
|
|
116
|
+
"sha256",
|
|
117
|
+
Buffer.from(tsk),
|
|
118
|
+
saltBuf,
|
|
119
|
+
INFO,
|
|
120
|
+
32,
|
|
121
|
+
(err, derivedKey) => {
|
|
122
|
+
if (err) return reject(err);
|
|
123
|
+
resolve(Buffer.from(derivedKey));
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function decryptSession(ciphertext, key) {
|
|
129
|
+
try {
|
|
130
|
+
const buf = Buffer.from(ciphertext, "base64");
|
|
131
|
+
if (buf.length < 12 + 16) return null;
|
|
132
|
+
const nonce = buf.subarray(0, 12);
|
|
133
|
+
const authTag = buf.subarray(buf.length - 16);
|
|
134
|
+
const encrypted = buf.subarray(12, buf.length - 16);
|
|
135
|
+
const decipher = import_crypto.default.createDecipheriv("aes-256-gcm", key, nonce);
|
|
136
|
+
decipher.setAuthTag(authTag);
|
|
137
|
+
const decrypted = Buffer.concat([
|
|
138
|
+
decipher.update(encrypted),
|
|
139
|
+
decipher.final()
|
|
140
|
+
]);
|
|
141
|
+
return JSON.parse(decrypted.toString("utf8"));
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function decryptSessionWithKeys(ciphertext, keys) {
|
|
147
|
+
for (const key of keys) {
|
|
148
|
+
const result = decryptSession(ciphertext, key);
|
|
149
|
+
if (result) return result;
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
105
154
|
// src/index.ts
|
|
106
|
-
var dauth = ({
|
|
155
|
+
var dauth = ({
|
|
156
|
+
domainName,
|
|
157
|
+
tsk,
|
|
158
|
+
cache,
|
|
159
|
+
session
|
|
160
|
+
}) => {
|
|
107
161
|
const userCache = cache ? new UserCache(cache) : null;
|
|
162
|
+
let keysPromise = null;
|
|
163
|
+
async function getEncKeys() {
|
|
164
|
+
if (!keysPromise) {
|
|
165
|
+
keysPromise = (async () => {
|
|
166
|
+
const keys = [];
|
|
167
|
+
keys.push(
|
|
168
|
+
await deriveEncryptionKey(tsk, session?.sessionSalt)
|
|
169
|
+
);
|
|
170
|
+
if (session?.previousTsk) {
|
|
171
|
+
keys.push(
|
|
172
|
+
await deriveEncryptionKey(
|
|
173
|
+
session.previousTsk,
|
|
174
|
+
session.sessionSalt
|
|
175
|
+
)
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
return keys;
|
|
179
|
+
})();
|
|
180
|
+
}
|
|
181
|
+
return keysPromise;
|
|
182
|
+
}
|
|
183
|
+
function getSessionCookieName() {
|
|
184
|
+
if (session?.cookieName) return session.cookieName;
|
|
185
|
+
const secure = session?.secure ?? process.env.NODE_ENV !== "development";
|
|
186
|
+
return secure ? "__Host-dauth-session" : "dauth-session";
|
|
187
|
+
}
|
|
108
188
|
return async (req, res, next) => {
|
|
109
|
-
|
|
110
|
-
|
|
189
|
+
let token;
|
|
190
|
+
if (session) {
|
|
191
|
+
const cookieName = getSessionCookieName();
|
|
192
|
+
const cookie = req.cookies?.[cookieName];
|
|
193
|
+
if (!cookie) {
|
|
194
|
+
return res.status(401).send({
|
|
195
|
+
status: "no-session",
|
|
196
|
+
message: "Not authenticated"
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const keys = await getEncKeys();
|
|
200
|
+
const payload = decryptSessionWithKeys(cookie, keys);
|
|
201
|
+
if (!payload) {
|
|
202
|
+
return res.status(401).send({
|
|
203
|
+
status: "session-invalid",
|
|
204
|
+
message: "Invalid session"
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
token = payload.accessToken;
|
|
208
|
+
} else {
|
|
209
|
+
if (!req.headers.authorization) {
|
|
210
|
+
return res.status(403).send({
|
|
211
|
+
status: "token-not-found",
|
|
212
|
+
message: "Token not found"
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
token = req.headers.authorization.replace(/['"]+/g, "");
|
|
111
216
|
}
|
|
112
|
-
const token = req.headers.authorization.replace(/['"]+/g, "");
|
|
113
217
|
try {
|
|
114
218
|
import_jsonwebtoken.default.verify(token, tsk);
|
|
115
219
|
} catch (error) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/api/utils/config.ts","../src/api/dauth.api.ts","../src/cache.ts"],"sourcesContent":["import { Request, NextFunction, Response as ExpressResponse } from 'express';\nimport jwt from 'jsonwebtoken';\nimport { getUser } from './api/dauth.api';\nimport { UserCache } from './cache';\nimport type { CacheOptions } from './cache';\n\nexport type AuthMethodType = 'magic-link' | 'passkey';\n\nexport interface IDauthUser {\n _id: string;\n name: string;\n lastname: string;\n nickname: string;\n email: string;\n isVerified: boolean;\n language: string;\n avatar: {\n id: string;\n url: string;\n };\n role: string;\n telPrefix: string;\n telSuffix: string;\n birthDate?: string;\n country?: string;\n metadata?: Record<string, unknown>;\n authMethods?: AuthMethodType[];\n createdAt: Date;\n updatedAt: Date;\n lastLogin: Date;\n}\n\nexport interface IRequestDauth extends Request {\n user: IDauthUser;\n files: {\n image: { path: string };\n avatar: { path: string };\n };\n headers: {\n authorization: string;\n };\n}\n\nexport interface DauthOptions {\n domainName: string;\n tsk: string;\n cache?: CacheOptions;\n}\n\ninterface TCustomResponse extends ExpressResponse {\n status(code: number): this;\n send(body?: unknown): this;\n}\n\nexport { UserCache };\nexport type { CacheOptions };\n\nexport const dauth = ({ domainName, tsk, cache }: DauthOptions) => {\n const userCache = cache ? new UserCache(cache) : null;\n\n return async (\n req: IRequestDauth,\n res: TCustomResponse,\n next: NextFunction\n ) => {\n if (!req.headers.authorization) {\n return res\n .status(403)\n .send({ status: 'token-not-found', message: 'Token not found' });\n }\n\n const token = req.headers.authorization.replace(/['\"]+/g, '');\n\n try {\n jwt.verify(token, tsk);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Token invalid';\n\n if (message === 'jwt expired') {\n return res\n .status(401)\n .send({ status: 'token-expired', message: 'jwt expired' });\n }\n if (message === 'invalid signature') {\n return res.status(401).send({\n status: 'tsk-not-invalid',\n message: 'The TSK variable in the backend middleware is not valid',\n });\n }\n return res.status(401).send({ status: 'token-invalid', message });\n }\n\n if (userCache) {\n const cachedUser = userCache.get(token);\n if (cachedUser) {\n req.user = cachedUser;\n return next();\n }\n }\n\n try {\n const getUserFetch = await getUser(token, domainName);\n\n if (getUserFetch.response.status === 404) {\n return res.status(404).send({\n status: 'user-not-found',\n message: getUserFetch.data.message ?? 'User does not exist',\n });\n }\n if (getUserFetch.response.status === 500) {\n return res.status(500).send({\n status: 'error',\n message: getUserFetch.data.message ?? 'Dauth server error',\n });\n }\n if (getUserFetch.response.status === 200) {\n req.user = getUserFetch.data.user;\n if (userCache) {\n userCache.set(token, req.user);\n }\n return next();\n }\n return res.status(501).send({\n status: 'request-error',\n message: getUserFetch.data.message ?? 'Dauth server error',\n });\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Dauth server error';\n return res.status(500).send({ status: 'server-error', message });\n }\n };\n};\n","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 { getServerBasePath } from './utils/config';\n\ninterface GetUserResponse {\n response: { status: number };\n data: { user?: any; message?: string };\n}\n\nexport async function getUser(\n token: string,\n domainName: string\n): Promise<GetUserResponse> {\n const response = await fetch(\n `${getServerBasePath()}/app/${domainName}/user`,\n {\n method: 'GET',\n headers: {\n Authorization: token,\n 'Content-Type': 'application/json',\n },\n }\n );\n const data = (await response.json()) as GetUserResponse['data'];\n return { response: { status: response.status }, data };\n}\n","import { IDauthUser } from './index';\n\ninterface CacheEntry {\n user: IDauthUser;\n expiresAt: number;\n}\n\nexport interface CacheOptions {\n ttlMs: number;\n}\n\nexport class UserCache {\n private store = new Map<string, CacheEntry>();\n private ttlMs: number;\n\n constructor(options: CacheOptions) {\n this.ttlMs = options.ttlMs;\n }\n\n get(token: string): IDauthUser | undefined {\n const entry = this.store.get(token);\n if (!entry) return undefined;\n\n if (Date.now() > entry.expiresAt) {\n this.store.delete(token);\n return undefined;\n }\n\n return entry.user;\n }\n\n set(token: string, user: IDauthUser): void {\n if (this.store.size > 1000) {\n this.sweep();\n }\n this.store.set(token, { user, expiresAt: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n\n private sweep(): void {\n const now = Date.now();\n for (const [key, entry] of this.store) {\n if (now > entry.expiresAt) {\n this.store.delete(key);\n }\n }\n }\n}\n"],"mappings":"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","names":["jwt"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/api/utils/config.ts","../src/api/dauth.api.ts","../src/cache.ts","../src/session.ts"],"sourcesContent":["import { Request, NextFunction, Response as ExpressResponse } from 'express';\nimport jwt from 'jsonwebtoken';\nimport { getUser } from './api/dauth.api';\nimport { UserCache } from './cache';\nimport type { CacheOptions } from './cache';\nimport { deriveEncryptionKey, decryptSessionWithKeys } from './session';\n\nexport type AuthMethodType = 'magic-link' | 'passkey';\n\nexport interface IDauthUser {\n _id: string;\n name: string;\n lastname: string;\n nickname: string;\n email: string;\n isVerified: boolean;\n language: string;\n avatar: {\n id: string;\n url: string;\n };\n role: string;\n telPrefix: string;\n telSuffix: string;\n birthDate?: string;\n country?: string;\n metadata?: Record<string, unknown>;\n authMethods?: AuthMethodType[];\n createdAt: Date;\n updatedAt: Date;\n lastLogin: Date;\n}\n\nexport interface IRequestDauth extends Request {\n user: IDauthUser;\n files: {\n image: { path: string };\n avatar: { path: string };\n };\n headers: {\n authorization: string;\n };\n}\n\nexport interface SessionOptions {\n cookieName?: string;\n secure?: boolean;\n previousTsk?: string;\n sessionSalt?: string;\n}\n\nexport interface DauthOptions {\n domainName: string;\n tsk: string;\n cache?: CacheOptions;\n session?: SessionOptions;\n}\n\ninterface TCustomResponse extends ExpressResponse {\n status(code: number): this;\n send(body?: unknown): this;\n}\n\nexport { UserCache };\nexport type { CacheOptions };\n\nexport const dauth = ({\n domainName,\n tsk,\n cache,\n session,\n}: DauthOptions) => {\n const userCache = cache ? new UserCache(cache) : null;\n\n // Lazy-init encryption keys for session cookie mode\n let keysPromise: Promise<Buffer[]> | null = null;\n async function getEncKeys(): Promise<Buffer[]> {\n if (!keysPromise) {\n keysPromise = (async () => {\n const keys: Buffer[] = [];\n keys.push(\n await deriveEncryptionKey(tsk, session?.sessionSalt)\n );\n if (session?.previousTsk) {\n keys.push(\n await deriveEncryptionKey(\n session.previousTsk,\n session.sessionSalt\n )\n );\n }\n return keys;\n })();\n }\n return keysPromise;\n }\n\n function getSessionCookieName(): string {\n if (session?.cookieName) return session.cookieName;\n const secure =\n session?.secure ?? process.env.NODE_ENV !== 'development';\n return secure ? '__Host-dauth-session' : 'dauth-session';\n }\n\n return async (\n req: IRequestDauth,\n res: TCustomResponse,\n next: NextFunction\n ) => {\n let token: string;\n\n if (session) {\n // Session cookie mode: read encrypted cookie\n const cookieName = getSessionCookieName();\n const cookie = req.cookies?.[cookieName];\n if (!cookie) {\n return res\n .status(401)\n .send({\n status: 'no-session',\n message: 'Not authenticated',\n });\n }\n const keys = await getEncKeys();\n const payload = decryptSessionWithKeys(cookie, keys);\n if (!payload) {\n return res\n .status(401)\n .send({\n status: 'session-invalid',\n message: 'Invalid session',\n });\n }\n token = payload.accessToken;\n } else {\n // Authorization header mode\n if (!req.headers.authorization) {\n return res\n .status(403)\n .send({\n status: 'token-not-found',\n message: 'Token not found',\n });\n }\n token = req.headers.authorization.replace(/['\"]+/g, '');\n }\n\n try {\n jwt.verify(token, tsk);\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Token invalid';\n\n if (message === 'jwt expired') {\n return res\n .status(401)\n .send({ status: 'token-expired', message: 'jwt expired' });\n }\n if (message === 'invalid signature') {\n return res.status(401).send({\n status: 'tsk-not-invalid',\n message:\n 'The TSK variable in the backend middleware is not valid',\n });\n }\n return res\n .status(401)\n .send({ status: 'token-invalid', message });\n }\n\n if (userCache) {\n const cachedUser = userCache.get(token);\n if (cachedUser) {\n req.user = cachedUser;\n return next();\n }\n }\n\n try {\n const getUserFetch = await getUser(token, domainName);\n\n if (getUserFetch.response.status === 404) {\n return res.status(404).send({\n status: 'user-not-found',\n message:\n getUserFetch.data.message ?? 'User does not exist',\n });\n }\n if (getUserFetch.response.status === 500) {\n return res.status(500).send({\n status: 'error',\n message:\n getUserFetch.data.message ?? 'Dauth server error',\n });\n }\n if (getUserFetch.response.status === 200) {\n req.user = getUserFetch.data.user;\n if (userCache) {\n userCache.set(token, req.user);\n }\n return next();\n }\n return res.status(501).send({\n status: 'request-error',\n message:\n getUserFetch.data.message ?? 'Dauth server error',\n });\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : 'Dauth server error';\n return res\n .status(500)\n .send({ status: 'server-error', message });\n }\n };\n};\n","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 { getServerBasePath } from './utils/config';\n\ninterface GetUserResponse {\n response: { status: number };\n data: { user?: any; message?: string };\n}\n\nexport async function getUser(\n token: string,\n domainName: string\n): Promise<GetUserResponse> {\n const response = await fetch(\n `${getServerBasePath()}/app/${domainName}/user`,\n {\n method: 'GET',\n headers: {\n Authorization: token,\n 'Content-Type': 'application/json',\n },\n }\n );\n const data = (await response.json()) as GetUserResponse['data'];\n return { response: { status: response.status }, data };\n}\n","import { IDauthUser } from './index';\n\ninterface CacheEntry {\n user: IDauthUser;\n expiresAt: number;\n}\n\nexport interface CacheOptions {\n ttlMs: number;\n}\n\nexport class UserCache {\n private store = new Map<string, CacheEntry>();\n private ttlMs: number;\n\n constructor(options: CacheOptions) {\n this.ttlMs = options.ttlMs;\n }\n\n get(token: string): IDauthUser | undefined {\n const entry = this.store.get(token);\n if (!entry) return undefined;\n\n if (Date.now() > entry.expiresAt) {\n this.store.delete(token);\n return undefined;\n }\n\n return entry.user;\n }\n\n set(token: string, user: IDauthUser): void {\n if (this.store.size > 1000) {\n this.sweep();\n }\n this.store.set(token, { user, expiresAt: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n\n private sweep(): void {\n const now = Date.now();\n for (const [key, entry] of this.store) {\n if (now > entry.expiresAt) {\n this.store.delete(key);\n }\n }\n }\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":"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","names":["crypto","crypto","jwt"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,21 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
decryptSessionWithKeys,
|
|
3
|
+
deriveEncryptionKey,
|
|
4
|
+
getServerBasePath
|
|
5
|
+
} from "./chunk-4A7BR4EM.mjs";
|
|
6
|
+
|
|
1
7
|
// src/index.ts
|
|
2
8
|
import jwt from "jsonwebtoken";
|
|
3
9
|
|
|
4
|
-
// src/api/utils/config.ts
|
|
5
|
-
var apiVersion = "v1";
|
|
6
|
-
var serverDomain = "dauth.ovh";
|
|
7
|
-
function getServerBasePath() {
|
|
8
|
-
if (process.env.DAUTH_URL) {
|
|
9
|
-
const base = process.env.DAUTH_URL.replace(/\/+$/, "");
|
|
10
|
-
return `${base}/api/${apiVersion}`;
|
|
11
|
-
}
|
|
12
|
-
const isLocalhost = process.env.NODE_ENV === "development";
|
|
13
|
-
const serverPort = 4012;
|
|
14
|
-
const serverLocalUrl = `http://localhost:${serverPort}/api/${apiVersion}`;
|
|
15
|
-
const serverProdUrl = `https://${serverDomain}/api/${apiVersion}`;
|
|
16
|
-
return isLocalhost ? serverLocalUrl : serverProdUrl;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
10
|
// src/api/dauth.api.ts
|
|
20
11
|
async function getUser(token, domainName) {
|
|
21
12
|
const response = await fetch(
|
|
@@ -68,13 +59,68 @@ var UserCache = class {
|
|
|
68
59
|
};
|
|
69
60
|
|
|
70
61
|
// src/index.ts
|
|
71
|
-
var dauth = ({
|
|
62
|
+
var dauth = ({
|
|
63
|
+
domainName,
|
|
64
|
+
tsk,
|
|
65
|
+
cache,
|
|
66
|
+
session
|
|
67
|
+
}) => {
|
|
72
68
|
const userCache = cache ? new UserCache(cache) : null;
|
|
69
|
+
let keysPromise = null;
|
|
70
|
+
async function getEncKeys() {
|
|
71
|
+
if (!keysPromise) {
|
|
72
|
+
keysPromise = (async () => {
|
|
73
|
+
const keys = [];
|
|
74
|
+
keys.push(
|
|
75
|
+
await deriveEncryptionKey(tsk, session?.sessionSalt)
|
|
76
|
+
);
|
|
77
|
+
if (session?.previousTsk) {
|
|
78
|
+
keys.push(
|
|
79
|
+
await deriveEncryptionKey(
|
|
80
|
+
session.previousTsk,
|
|
81
|
+
session.sessionSalt
|
|
82
|
+
)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return keys;
|
|
86
|
+
})();
|
|
87
|
+
}
|
|
88
|
+
return keysPromise;
|
|
89
|
+
}
|
|
90
|
+
function getSessionCookieName() {
|
|
91
|
+
if (session?.cookieName) return session.cookieName;
|
|
92
|
+
const secure = session?.secure ?? process.env.NODE_ENV !== "development";
|
|
93
|
+
return secure ? "__Host-dauth-session" : "dauth-session";
|
|
94
|
+
}
|
|
73
95
|
return async (req, res, next) => {
|
|
74
|
-
|
|
75
|
-
|
|
96
|
+
let token;
|
|
97
|
+
if (session) {
|
|
98
|
+
const cookieName = getSessionCookieName();
|
|
99
|
+
const cookie = req.cookies?.[cookieName];
|
|
100
|
+
if (!cookie) {
|
|
101
|
+
return res.status(401).send({
|
|
102
|
+
status: "no-session",
|
|
103
|
+
message: "Not authenticated"
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const keys = await getEncKeys();
|
|
107
|
+
const payload = decryptSessionWithKeys(cookie, keys);
|
|
108
|
+
if (!payload) {
|
|
109
|
+
return res.status(401).send({
|
|
110
|
+
status: "session-invalid",
|
|
111
|
+
message: "Invalid session"
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
token = payload.accessToken;
|
|
115
|
+
} else {
|
|
116
|
+
if (!req.headers.authorization) {
|
|
117
|
+
return res.status(403).send({
|
|
118
|
+
status: "token-not-found",
|
|
119
|
+
message: "Token not found"
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
token = req.headers.authorization.replace(/['"]+/g, "");
|
|
76
123
|
}
|
|
77
|
-
const token = req.headers.authorization.replace(/['"]+/g, "");
|
|
78
124
|
try {
|
|
79
125
|
jwt.verify(token, tsk);
|
|
80
126
|
} catch (error) {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/api/utils/config.ts","../src/api/dauth.api.ts","../src/cache.ts"],"sourcesContent":["import { Request, NextFunction, Response as ExpressResponse } from 'express';\nimport jwt from 'jsonwebtoken';\nimport { getUser } from './api/dauth.api';\nimport { UserCache } from './cache';\nimport type { CacheOptions } from './cache';\n\nexport type AuthMethodType = 'magic-link' | 'passkey';\n\nexport interface IDauthUser {\n _id: string;\n name: string;\n lastname: string;\n nickname: string;\n email: string;\n isVerified: boolean;\n language: string;\n avatar: {\n id: string;\n url: string;\n };\n role: string;\n telPrefix: string;\n telSuffix: string;\n birthDate?: string;\n country?: string;\n metadata?: Record<string, unknown>;\n authMethods?: AuthMethodType[];\n createdAt: Date;\n updatedAt: Date;\n lastLogin: Date;\n}\n\nexport interface IRequestDauth extends Request {\n user: IDauthUser;\n files: {\n image: { path: string };\n avatar: { path: string };\n };\n headers: {\n authorization: string;\n };\n}\n\nexport interface DauthOptions {\n domainName: string;\n tsk: string;\n cache?: CacheOptions;\n}\n\ninterface TCustomResponse extends ExpressResponse {\n status(code: number): this;\n send(body?: unknown): this;\n}\n\nexport { UserCache };\nexport type { CacheOptions };\n\nexport const dauth = ({ domainName, tsk, cache }: DauthOptions) => {\n const userCache = cache ? new UserCache(cache) : null;\n\n return async (\n req: IRequestDauth,\n res: TCustomResponse,\n next: NextFunction\n ) => {\n if (!req.headers.authorization) {\n return res\n .status(403)\n .send({ status: 'token-not-found', message: 'Token not found' });\n }\n\n const token = req.headers.authorization.replace(/['\"]+/g, '');\n\n try {\n jwt.verify(token, tsk);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Token invalid';\n\n if (message === 'jwt expired') {\n return res\n .status(401)\n .send({ status: 'token-expired', message: 'jwt expired' });\n }\n if (message === 'invalid signature') {\n return res.status(401).send({\n status: 'tsk-not-invalid',\n message: 'The TSK variable in the backend middleware is not valid',\n });\n }\n return res.status(401).send({ status: 'token-invalid', message });\n }\n\n if (userCache) {\n const cachedUser = userCache.get(token);\n if (cachedUser) {\n req.user = cachedUser;\n return next();\n }\n }\n\n try {\n const getUserFetch = await getUser(token, domainName);\n\n if (getUserFetch.response.status === 404) {\n return res.status(404).send({\n status: 'user-not-found',\n message: getUserFetch.data.message ?? 'User does not exist',\n });\n }\n if (getUserFetch.response.status === 500) {\n return res.status(500).send({\n status: 'error',\n message: getUserFetch.data.message ?? 'Dauth server error',\n });\n }\n if (getUserFetch.response.status === 200) {\n req.user = getUserFetch.data.user;\n if (userCache) {\n userCache.set(token, req.user);\n }\n return next();\n }\n return res.status(501).send({\n status: 'request-error',\n message: getUserFetch.data.message ?? 'Dauth server error',\n });\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Dauth server error';\n return res.status(500).send({ status: 'server-error', message });\n }\n };\n};\n","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 { getServerBasePath } from './utils/config';\n\ninterface GetUserResponse {\n response: { status: number };\n data: { user?: any; message?: string };\n}\n\nexport async function getUser(\n token: string,\n domainName: string\n): Promise<GetUserResponse> {\n const response = await fetch(\n `${getServerBasePath()}/app/${domainName}/user`,\n {\n method: 'GET',\n headers: {\n Authorization: token,\n 'Content-Type': 'application/json',\n },\n }\n );\n const data = (await response.json()) as GetUserResponse['data'];\n return { response: { status: response.status }, data };\n}\n","import { IDauthUser } from './index';\n\ninterface CacheEntry {\n user: IDauthUser;\n expiresAt: number;\n}\n\nexport interface CacheOptions {\n ttlMs: number;\n}\n\nexport class UserCache {\n private store = new Map<string, CacheEntry>();\n private ttlMs: number;\n\n constructor(options: CacheOptions) {\n this.ttlMs = options.ttlMs;\n }\n\n get(token: string): IDauthUser | undefined {\n const entry = this.store.get(token);\n if (!entry) return undefined;\n\n if (Date.now() > entry.expiresAt) {\n this.store.delete(token);\n return undefined;\n }\n\n return entry.user;\n }\n\n set(token: string, user: IDauthUser): void {\n if (this.store.size > 1000) {\n this.sweep();\n }\n this.store.set(token, { user, expiresAt: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n\n private sweep(): void {\n const now = Date.now();\n for (const [key, entry] of this.store) {\n if (now > entry.expiresAt) {\n this.store.delete(key);\n }\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/api/dauth.api.ts","../src/cache.ts"],"sourcesContent":["import { Request, NextFunction, Response as ExpressResponse } from 'express';\nimport jwt from 'jsonwebtoken';\nimport { getUser } from './api/dauth.api';\nimport { UserCache } from './cache';\nimport type { CacheOptions } from './cache';\nimport { deriveEncryptionKey, decryptSessionWithKeys } from './session';\n\nexport type AuthMethodType = 'magic-link' | 'passkey';\n\nexport interface IDauthUser {\n _id: string;\n name: string;\n lastname: string;\n nickname: string;\n email: string;\n isVerified: boolean;\n language: string;\n avatar: {\n id: string;\n url: string;\n };\n role: string;\n telPrefix: string;\n telSuffix: string;\n birthDate?: string;\n country?: string;\n metadata?: Record<string, unknown>;\n authMethods?: AuthMethodType[];\n createdAt: Date;\n updatedAt: Date;\n lastLogin: Date;\n}\n\nexport interface IRequestDauth extends Request {\n user: IDauthUser;\n files: {\n image: { path: string };\n avatar: { path: string };\n };\n headers: {\n authorization: string;\n };\n}\n\nexport interface SessionOptions {\n cookieName?: string;\n secure?: boolean;\n previousTsk?: string;\n sessionSalt?: string;\n}\n\nexport interface DauthOptions {\n domainName: string;\n tsk: string;\n cache?: CacheOptions;\n session?: SessionOptions;\n}\n\ninterface TCustomResponse extends ExpressResponse {\n status(code: number): this;\n send(body?: unknown): this;\n}\n\nexport { UserCache };\nexport type { CacheOptions };\n\nexport const dauth = ({\n domainName,\n tsk,\n cache,\n session,\n}: DauthOptions) => {\n const userCache = cache ? new UserCache(cache) : null;\n\n // Lazy-init encryption keys for session cookie mode\n let keysPromise: Promise<Buffer[]> | null = null;\n async function getEncKeys(): Promise<Buffer[]> {\n if (!keysPromise) {\n keysPromise = (async () => {\n const keys: Buffer[] = [];\n keys.push(\n await deriveEncryptionKey(tsk, session?.sessionSalt)\n );\n if (session?.previousTsk) {\n keys.push(\n await deriveEncryptionKey(\n session.previousTsk,\n session.sessionSalt\n )\n );\n }\n return keys;\n })();\n }\n return keysPromise;\n }\n\n function getSessionCookieName(): string {\n if (session?.cookieName) return session.cookieName;\n const secure =\n session?.secure ?? process.env.NODE_ENV !== 'development';\n return secure ? '__Host-dauth-session' : 'dauth-session';\n }\n\n return async (\n req: IRequestDauth,\n res: TCustomResponse,\n next: NextFunction\n ) => {\n let token: string;\n\n if (session) {\n // Session cookie mode: read encrypted cookie\n const cookieName = getSessionCookieName();\n const cookie = req.cookies?.[cookieName];\n if (!cookie) {\n return res\n .status(401)\n .send({\n status: 'no-session',\n message: 'Not authenticated',\n });\n }\n const keys = await getEncKeys();\n const payload = decryptSessionWithKeys(cookie, keys);\n if (!payload) {\n return res\n .status(401)\n .send({\n status: 'session-invalid',\n message: 'Invalid session',\n });\n }\n token = payload.accessToken;\n } else {\n // Authorization header mode\n if (!req.headers.authorization) {\n return res\n .status(403)\n .send({\n status: 'token-not-found',\n message: 'Token not found',\n });\n }\n token = req.headers.authorization.replace(/['\"]+/g, '');\n }\n\n try {\n jwt.verify(token, tsk);\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Token invalid';\n\n if (message === 'jwt expired') {\n return res\n .status(401)\n .send({ status: 'token-expired', message: 'jwt expired' });\n }\n if (message === 'invalid signature') {\n return res.status(401).send({\n status: 'tsk-not-invalid',\n message:\n 'The TSK variable in the backend middleware is not valid',\n });\n }\n return res\n .status(401)\n .send({ status: 'token-invalid', message });\n }\n\n if (userCache) {\n const cachedUser = userCache.get(token);\n if (cachedUser) {\n req.user = cachedUser;\n return next();\n }\n }\n\n try {\n const getUserFetch = await getUser(token, domainName);\n\n if (getUserFetch.response.status === 404) {\n return res.status(404).send({\n status: 'user-not-found',\n message:\n getUserFetch.data.message ?? 'User does not exist',\n });\n }\n if (getUserFetch.response.status === 500) {\n return res.status(500).send({\n status: 'error',\n message:\n getUserFetch.data.message ?? 'Dauth server error',\n });\n }\n if (getUserFetch.response.status === 200) {\n req.user = getUserFetch.data.user;\n if (userCache) {\n userCache.set(token, req.user);\n }\n return next();\n }\n return res.status(501).send({\n status: 'request-error',\n message:\n getUserFetch.data.message ?? 'Dauth server error',\n });\n } catch (error) {\n const message =\n error instanceof Error\n ? error.message\n : 'Dauth server error';\n return res\n .status(500)\n .send({ status: 'server-error', message });\n }\n };\n};\n","import { getServerBasePath } from './utils/config';\n\ninterface GetUserResponse {\n response: { status: number };\n data: { user?: any; message?: string };\n}\n\nexport async function getUser(\n token: string,\n domainName: string\n): Promise<GetUserResponse> {\n const response = await fetch(\n `${getServerBasePath()}/app/${domainName}/user`,\n {\n method: 'GET',\n headers: {\n Authorization: token,\n 'Content-Type': 'application/json',\n },\n }\n );\n const data = (await response.json()) as GetUserResponse['data'];\n return { response: { status: response.status }, data };\n}\n","import { IDauthUser } from './index';\n\ninterface CacheEntry {\n user: IDauthUser;\n expiresAt: number;\n}\n\nexport interface CacheOptions {\n ttlMs: number;\n}\n\nexport class UserCache {\n private store = new Map<string, CacheEntry>();\n private ttlMs: number;\n\n constructor(options: CacheOptions) {\n this.ttlMs = options.ttlMs;\n }\n\n get(token: string): IDauthUser | undefined {\n const entry = this.store.get(token);\n if (!entry) return undefined;\n\n if (Date.now() > entry.expiresAt) {\n this.store.delete(token);\n return undefined;\n }\n\n return entry.user;\n }\n\n set(token: string, user: IDauthUser): void {\n if (this.store.size > 1000) {\n this.sweep();\n }\n this.store.set(token, { user, expiresAt: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.store.clear();\n }\n\n private sweep(): void {\n const now = Date.now();\n for (const [key, entry] of this.store) {\n if (now > entry.expiresAt) {\n this.store.delete(key);\n }\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
|
|
3
|
+
interface DauthRouterOptions {
|
|
4
|
+
domainName: string;
|
|
5
|
+
tsk: string;
|
|
6
|
+
dauthUrl?: string;
|
|
7
|
+
cookieName?: string;
|
|
8
|
+
csrfCookieName?: string;
|
|
9
|
+
maxAge?: number;
|
|
10
|
+
secure?: boolean;
|
|
11
|
+
previousTsk?: string;
|
|
12
|
+
sessionSalt?: string;
|
|
13
|
+
}
|
|
14
|
+
declare function dauthRouter(opts: DauthRouterOptions): Router;
|
|
15
|
+
|
|
16
|
+
export { type DauthRouterOptions, dauthRouter };
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
|
|
3
|
+
interface DauthRouterOptions {
|
|
4
|
+
domainName: string;
|
|
5
|
+
tsk: string;
|
|
6
|
+
dauthUrl?: string;
|
|
7
|
+
cookieName?: string;
|
|
8
|
+
csrfCookieName?: string;
|
|
9
|
+
maxAge?: number;
|
|
10
|
+
secure?: boolean;
|
|
11
|
+
previousTsk?: string;
|
|
12
|
+
sessionSalt?: string;
|
|
13
|
+
}
|
|
14
|
+
declare function dauthRouter(opts: DauthRouterOptions): Router;
|
|
15
|
+
|
|
16
|
+
export { type DauthRouterOptions, dauthRouter };
|