peta-auth 0.1.3 → 0.2.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/crypto-DR-ETdLZ.d.mts +35 -0
- package/dist/crypto-WcFV83Nz.mjs +84 -0
- package/dist/csrf.d.mts +31 -2
- package/dist/csrf.mjs +34 -2
- package/dist/elysia.d.mts +22 -1
- package/dist/elysia.mjs +16 -7
- package/dist/errors-DxJ-WUJL.mjs +17 -0
- package/dist/hono.d.mts +22 -1
- package/dist/hono.mjs +13 -3
- package/dist/index.d.mts +59 -5
- package/dist/index.mjs +56 -8
- package/dist/jwt.d.mts +23 -2
- package/dist/jwt.mjs +29 -9
- package/dist/nuxt.d.mts +26 -3
- package/dist/nuxt.mjs +18 -6
- package/dist/oauth/github.d.mts +18 -5
- package/dist/oauth/github.mjs +62 -71
- package/dist/oauth/google.d.mts +20 -7
- package/dist/oauth/google.mjs +65 -75
- package/dist/session-0bF8_7Ui.d.mts +53 -0
- package/dist/session-BGCQ1Z1Q.mjs +87 -0
- package/dist/utils-CKT3C1Lq.mjs +174 -0
- package/package.json +11 -7
- package/dist/crypto-Ln_Mj_zp.d.mts +0 -19
- package/dist/oauth/index.d.mts +0 -25
- package/dist/oauth/index.mjs +0 -103
- package/dist/session-DSwf3XPH.mjs +0 -119
- package/dist/session-z20gaFVT.d.mts +0 -23
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//#region src/crypto.d.ts
|
|
2
|
+
/** A password that can be a plain string or a versioned map. */
|
|
3
|
+
type Password = string | Record<string, string>;
|
|
4
|
+
/**
|
|
5
|
+
* Seal arbitrary data with a password (uses iron-webcrypto).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const sealed = await sealData({ userId: 1 }, { password: "my-secret-key" })
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
declare const sealData: (data: unknown, {
|
|
13
|
+
password,
|
|
14
|
+
ttl
|
|
15
|
+
}: {
|
|
16
|
+
password: Password;
|
|
17
|
+
ttl?: number;
|
|
18
|
+
}) => Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Unseal data previously sealed with {@link sealData}.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const data = await unsealData<{ userId: number }>(sealed, { password: "my-secret-key" })
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
declare const unsealData: <T>(seal: string, {
|
|
28
|
+
password,
|
|
29
|
+
ttl
|
|
30
|
+
}: {
|
|
31
|
+
password: Password;
|
|
32
|
+
ttl?: number;
|
|
33
|
+
}) => Promise<T>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { sealData as n, unsealData as r, Password as t };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { defaults, seal, unseal } from "iron-webcrypto";
|
|
2
|
+
//#region src/crypto.ts
|
|
3
|
+
const SEVEN_DAYS = 336 * 3600;
|
|
4
|
+
const CURRENT_MAJOR_VERSION = 2;
|
|
5
|
+
const VERSION_DELIMITER = "~";
|
|
6
|
+
function normalizePassword(password) {
|
|
7
|
+
return typeof password === "string" ? { 1: password } : password;
|
|
8
|
+
}
|
|
9
|
+
function parseSeal(seal) {
|
|
10
|
+
const index = seal.lastIndexOf(VERSION_DELIMITER);
|
|
11
|
+
if (index === -1) return {
|
|
12
|
+
sealWithoutVersion: seal,
|
|
13
|
+
tokenVersion: null
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
sealWithoutVersion: seal.slice(0, index),
|
|
17
|
+
tokenVersion: parseInt(seal.slice(index + 1), 10) || null
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create a `sealData` function.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
function createSealData() {
|
|
26
|
+
return async function sealData(data, { password, ttl = SEVEN_DAYS }) {
|
|
27
|
+
const map = normalizePassword(password);
|
|
28
|
+
const id = Math.max(...Object.keys(map).map(Number)).toString();
|
|
29
|
+
const secret = map[id];
|
|
30
|
+
return `${await seal(data, {
|
|
31
|
+
id,
|
|
32
|
+
secret
|
|
33
|
+
}, {
|
|
34
|
+
...defaults,
|
|
35
|
+
ttl: ttl * 1e3,
|
|
36
|
+
encode: JSON.stringify,
|
|
37
|
+
decode: JSON.parse
|
|
38
|
+
})}${VERSION_DELIMITER}${CURRENT_MAJOR_VERSION}`;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create an `unsealData` function.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
function createUnsealData() {
|
|
47
|
+
return async function unsealData(seal, { password, ttl = SEVEN_DAYS }) {
|
|
48
|
+
const map = normalizePassword(password);
|
|
49
|
+
const { sealWithoutVersion, tokenVersion } = parseSeal(seal);
|
|
50
|
+
try {
|
|
51
|
+
const data = await unseal(sealWithoutVersion, map, {
|
|
52
|
+
...defaults,
|
|
53
|
+
ttl: ttl * 1e3,
|
|
54
|
+
encode: JSON.stringify,
|
|
55
|
+
decode: JSON.parse
|
|
56
|
+
});
|
|
57
|
+
if (tokenVersion === 2) return data;
|
|
58
|
+
return { ...data?.persistent ? { ...data.persistent } : {} };
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (err instanceof Error && /^(Expired seal|Bad hmac value|Cannot find password|Incorrect number of sealed components)/.test(err.message)) return {};
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Seal arbitrary data with a password (uses iron-webcrypto).
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* const sealed = await sealData({ userId: 1 }, { password: "my-secret-key" })
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
const sealData = createSealData();
|
|
74
|
+
/**
|
|
75
|
+
* Unseal data previously sealed with {@link sealData}.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* const data = await unsealData<{ userId: number }>(sealed, { password: "my-secret-key" })
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
const unsealData = createUnsealData();
|
|
83
|
+
//#endregion
|
|
84
|
+
export { sealData as n, unsealData as r, normalizePassword as t };
|
package/dist/csrf.d.mts
CHANGED
|
@@ -1,10 +1,39 @@
|
|
|
1
|
-
import { t as IronSession } from "./session-
|
|
1
|
+
import { t as IronSession } from "./session-0bF8_7Ui.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/csrf.d.ts
|
|
4
|
+
/** Options for CSRF token generation / validation. */
|
|
4
5
|
interface CSRFOptions {
|
|
6
|
+
/** Key used to store the token in the session (default `"_csrfToken"`). */
|
|
5
7
|
key?: string;
|
|
6
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Constant-time string comparison to prevent timing side-channel attacks.
|
|
11
|
+
* Always iterates over the full length of the input, regardless of where
|
|
12
|
+
* the first difference occurs.
|
|
13
|
+
*/
|
|
14
|
+
declare function constantTimeEqual(a: string, b: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Generate a CSRF token and store it in the session.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const token = await generateCsrf(session)
|
|
21
|
+
* // send `token` to the client via form field or header
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
7
24
|
declare function generateCsrf(session: IronSession, options?: CSRFOptions): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Validate a CSRF token against the value stored in the session.
|
|
27
|
+
*
|
|
28
|
+
* Uses constant-time comparison to prevent timing side-channel attacks.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* if (!validateCsrf(session, submittedToken)) {
|
|
33
|
+
* // reject request
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
8
37
|
declare function validateCsrf(session: IronSession, token: string, options?: CSRFOptions): boolean;
|
|
9
38
|
//#endregion
|
|
10
|
-
export { CSRFOptions, generateCsrf, validateCsrf };
|
|
39
|
+
export { CSRFOptions, constantTimeEqual, generateCsrf, validateCsrf };
|
package/dist/csrf.mjs
CHANGED
|
@@ -1,13 +1,45 @@
|
|
|
1
1
|
//#region src/csrf.ts
|
|
2
|
+
/**
|
|
3
|
+
* Constant-time string comparison to prevent timing side-channel attacks.
|
|
4
|
+
* Always iterates over the full length of the input, regardless of where
|
|
5
|
+
* the first difference occurs.
|
|
6
|
+
*/
|
|
7
|
+
function constantTimeEqual(a, b) {
|
|
8
|
+
if (a.length !== b.length) return false;
|
|
9
|
+
let result = 0;
|
|
10
|
+
for (let i = 0; i < a.length; i++) result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
11
|
+
return result === 0;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Generate a CSRF token and store it in the session.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const token = await generateCsrf(session)
|
|
19
|
+
* // send `token` to the client via form field or header
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
2
22
|
async function generateCsrf(session, options) {
|
|
3
23
|
const key = options?.key ?? "_csrfToken";
|
|
4
24
|
const token = crypto.randomUUID();
|
|
5
25
|
session[key] = token;
|
|
6
26
|
return token;
|
|
7
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Validate a CSRF token against the value stored in the session.
|
|
30
|
+
*
|
|
31
|
+
* Uses constant-time comparison to prevent timing side-channel attacks.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* if (!validateCsrf(session, submittedToken)) {
|
|
36
|
+
* // reject request
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
8
40
|
function validateCsrf(session, token, options) {
|
|
9
41
|
const stored = session[options?.key ?? "_csrfToken"];
|
|
10
|
-
return typeof stored === "string" && stored
|
|
42
|
+
return typeof stored === "string" && constantTimeEqual(stored, token);
|
|
11
43
|
}
|
|
12
44
|
//#endregion
|
|
13
|
-
export { generateCsrf, validateCsrf };
|
|
45
|
+
export { constantTimeEqual, generateCsrf, validateCsrf };
|
package/dist/elysia.d.mts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import { r as SessionOptions, t as IronSession } from "./session-
|
|
1
|
+
import { r as SessionOptions, t as IronSession } from "./session-0bF8_7Ui.mjs";
|
|
2
2
|
import { Elysia } from "elysia";
|
|
3
3
|
|
|
4
4
|
//#region src/elysia.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Elysia plugin that provides a session via the `session` store property.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* app.use(session({ password: "...", cookieName: "my-session" }))
|
|
11
|
+
* app.get("/me", ({ session }) => session)
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
5
14
|
declare function session<T extends Record<string, unknown> = Record<string, unknown>>(options: SessionOptions): Elysia<"", {
|
|
6
15
|
decorator: {};
|
|
7
16
|
store: {};
|
|
@@ -34,6 +43,18 @@ declare function session<T extends Record<string, unknown> = Record<string, unkn
|
|
|
34
43
|
standaloneSchema: {};
|
|
35
44
|
response: {};
|
|
36
45
|
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Elysia guard (onBeforeHandle) that requires session data.
|
|
48
|
+
*
|
|
49
|
+
* Returns 401 when the session is empty.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* app.guard({ beforeHandle: requireSession() }, (app) =>
|
|
54
|
+
* app.get("/admin", () => "ok")
|
|
55
|
+
* )
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
37
58
|
declare function requireSession(): (app: Elysia) => Elysia;
|
|
38
59
|
declare function requireSession<K extends string>(key: K): (app: Elysia) => Elysia;
|
|
39
60
|
//#endregion
|
package/dist/elysia.mjs
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
|
-
import { t as createSessionFromAdapter } from "./session-
|
|
1
|
+
import { n as sessionHasData, t as createSessionFromAdapter } from "./session-BGCQ1Z1Q.mjs";
|
|
2
2
|
import { parse } from "cookie";
|
|
3
3
|
import { Elysia } from "elysia";
|
|
4
4
|
//#region src/elysia.ts
|
|
5
|
+
/**
|
|
6
|
+
* Elysia plugin that provides a session via the `session` store property.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* app.use(session({ password: "...", cookieName: "my-session" }))
|
|
11
|
+
* app.get("/me", ({ session }) => session)
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
5
14
|
function session(options) {
|
|
6
|
-
return new Elysia({ name: "peta-auth" }).derive({ as: "scoped" }, async ({ headers
|
|
7
|
-
const
|
|
15
|
+
return new Elysia({ name: "peta-auth" }).derive({ as: "scoped" }, async ({ headers, set }) => {
|
|
16
|
+
const cookieString = headers instanceof Headers ? headers.get("cookie") ?? "" : headers.cookie ?? "";
|
|
8
17
|
return { session: await createSessionFromAdapter({
|
|
9
|
-
getCookie: (name) => parse(
|
|
10
|
-
setCookie: (
|
|
11
|
-
set.headers["Set-Cookie"] =
|
|
18
|
+
getCookie: (name) => parse(cookieString)[name],
|
|
19
|
+
setCookie: (value) => {
|
|
20
|
+
set.headers["Set-Cookie"] = value;
|
|
12
21
|
}
|
|
13
22
|
}, options) };
|
|
14
23
|
});
|
|
@@ -16,7 +25,7 @@ function session(options) {
|
|
|
16
25
|
function requireSession(key) {
|
|
17
26
|
return (app) => app.onBeforeHandle((context) => {
|
|
18
27
|
const session = context.session;
|
|
19
|
-
if (!(
|
|
28
|
+
if (!sessionHasData(session, key)) return new Response(JSON.stringify({ error: "unauthorized" }), {
|
|
20
29
|
status: 401,
|
|
21
30
|
headers: { "Content-Type": "application/json" }
|
|
22
31
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Typed error for peta-auth.
|
|
4
|
+
*
|
|
5
|
+
* Carries a machine-readable `code` and a human-readable `message`.
|
|
6
|
+
* Thrown instead of raw `new Error(...)` throughout the library.
|
|
7
|
+
*/
|
|
8
|
+
var PetaAuthError = class extends Error {
|
|
9
|
+
code;
|
|
10
|
+
constructor(code, message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "PetaAuthError";
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { PetaAuthError as t };
|
package/dist/hono.d.mts
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
|
-
import { r as SessionOptions, t as IronSession } from "./session-
|
|
1
|
+
import { r as SessionOptions, t as IronSession } from "./session-0bF8_7Ui.mjs";
|
|
2
2
|
import { MiddlewareHandler } from "hono";
|
|
3
3
|
|
|
4
4
|
//#region src/hono.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Hono middleware that creates a session and makes it available
|
|
7
|
+
* via `c.var.session`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* app.use("*", session({ password: "...", cookieName: "my-session" }))
|
|
12
|
+
* app.get("/me", (c) => c.json(c.var.session))
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
5
15
|
declare function session<T extends Record<string, unknown> = Record<string, unknown>>(options: SessionOptions): MiddlewareHandler<{
|
|
6
16
|
Variables: {
|
|
7
17
|
session: T & IronSession;
|
|
8
18
|
};
|
|
9
19
|
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Hono middleware that guards a route by requiring session data.
|
|
22
|
+
*
|
|
23
|
+
* Returns 401 when the session is empty.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* app.use("/admin", requireSession())
|
|
28
|
+
* app.use("/admin", requireSession("role"))
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
10
31
|
declare function requireSession(): MiddlewareHandler;
|
|
11
32
|
declare function requireSession<K extends string>(key: K): MiddlewareHandler;
|
|
12
33
|
//#endregion
|
package/dist/hono.mjs
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import { t as createSessionFromAdapter } from "./session-
|
|
1
|
+
import { n as sessionHasData, t as createSessionFromAdapter } from "./session-BGCQ1Z1Q.mjs";
|
|
2
2
|
import { parse } from "cookie";
|
|
3
3
|
import { createMiddleware } from "hono/factory";
|
|
4
4
|
//#region src/hono.ts
|
|
5
|
+
/**
|
|
6
|
+
* Hono middleware that creates a session and makes it available
|
|
7
|
+
* via `c.var.session`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* app.use("*", session({ password: "...", cookieName: "my-session" }))
|
|
12
|
+
* app.get("/me", (c) => c.json(c.var.session))
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
5
15
|
function session(options) {
|
|
6
16
|
return createMiddleware(async (c, next) => {
|
|
7
17
|
c.set("session", await createSessionFromAdapter({
|
|
8
18
|
getCookie: (name) => parse(c.req.header("cookie") ?? "")[name],
|
|
9
|
-
setCookie: (
|
|
19
|
+
setCookie: (value) => c.res.headers.append("Set-Cookie", value)
|
|
10
20
|
}, options));
|
|
11
21
|
await next();
|
|
12
22
|
});
|
|
@@ -14,7 +24,7 @@ function session(options) {
|
|
|
14
24
|
function requireSession(key) {
|
|
15
25
|
return createMiddleware(async (c, next) => {
|
|
16
26
|
const s = c.var.session;
|
|
17
|
-
if (!(
|
|
27
|
+
if (!sessionHasData(s, key)) return c.json({ error: "unauthorized" }, 401);
|
|
18
28
|
await next();
|
|
19
29
|
});
|
|
20
30
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,27 +1,81 @@
|
|
|
1
|
-
import { n as sealData, r as unsealData, t as Password } from "./crypto-
|
|
2
|
-
import { i as createSessionFromAdapter, n as SessionAdapter, r as SessionOptions, t as IronSession } from "./session-
|
|
1
|
+
import { n as sealData, r as unsealData, t as Password } from "./crypto-DR-ETdLZ.mjs";
|
|
2
|
+
import { i as createSessionFromAdapter, n as SessionAdapter, r as SessionOptions, t as IronSession } from "./session-0bF8_7Ui.mjs";
|
|
3
3
|
import { CSRFOptions, generateCsrf, validateCsrf } from "./csrf.mjs";
|
|
4
4
|
import { JWTOptions, signJWT, verifyJWT } from "./jwt.mjs";
|
|
5
5
|
|
|
6
|
+
//#region src/errors.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Typed error for peta-auth.
|
|
9
|
+
*
|
|
10
|
+
* Carries a machine-readable `code` and a human-readable `message`.
|
|
11
|
+
* Thrown instead of raw `new Error(...)` throughout the library.
|
|
12
|
+
*/
|
|
13
|
+
declare class PetaAuthError extends Error {
|
|
14
|
+
readonly code: string;
|
|
15
|
+
constructor(code: string, message: string);
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
6
18
|
//#region src/password.d.ts
|
|
7
19
|
interface HashOptions {
|
|
8
|
-
cost
|
|
20
|
+
/** Memory cost in KiB (default: 19456 = 19 MiB). */
|
|
21
|
+
memoryCost?: number;
|
|
22
|
+
/** Time cost (iterations) (default: 2). */
|
|
23
|
+
timeCost?: number;
|
|
24
|
+
/** Parallelism (default: 1). */
|
|
25
|
+
parallelism?: number;
|
|
9
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Hash a password with argon2id.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const hash = await hashPassword("my-password")
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
10
35
|
declare function hashPassword(password: string, options?: HashOptions): Promise<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Verify a password against an argon2id hash.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const ok = await verifyPassword(hash, "my-password")
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
11
44
|
declare function verifyPassword(hash: string, password: string): Promise<boolean>;
|
|
12
45
|
//#endregion
|
|
13
46
|
//#region src/reset-password.d.ts
|
|
47
|
+
/** Options for password reset token generation. */
|
|
14
48
|
interface PasswordResetOptions {
|
|
49
|
+
/** Password(s) used to sign the reset token. */
|
|
15
50
|
password: Password;
|
|
16
|
-
|
|
51
|
+
/** Token lifetime in seconds (default 1 hour). */
|
|
52
|
+
expiresIn?: number;
|
|
17
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Create a password-reset token for a user.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const token = await createPasswordResetToken(userId, { password: "..." })
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
18
62
|
declare function createPasswordResetToken(userId: string, options: PasswordResetOptions): Promise<string>;
|
|
63
|
+
/**
|
|
64
|
+
* Verify a password-reset token.
|
|
65
|
+
*
|
|
66
|
+
* Returns the user ID when the token is valid, or `null` if expired/invalid.
|
|
67
|
+
*/
|
|
19
68
|
declare function verifyPasswordResetToken(token: string, password: Password): Promise<{
|
|
20
69
|
userId: string;
|
|
21
70
|
} | null>;
|
|
71
|
+
/**
|
|
72
|
+
* Verify a password-reset token and apply the new password.
|
|
73
|
+
*
|
|
74
|
+
* Returns `{ userId, hash }` on success, or `null` if the token is invalid.
|
|
75
|
+
*/
|
|
22
76
|
declare function resetPassword(token: string, newPassword: string, password: Password): Promise<{
|
|
23
77
|
userId: string;
|
|
24
78
|
hash: string;
|
|
25
79
|
} | null>;
|
|
26
80
|
//#endregion
|
|
27
|
-
export { type CSRFOptions, type IronSession, type JWTOptions, type Password, type PasswordResetOptions, type SessionAdapter, type SessionOptions, createPasswordResetToken, createSessionFromAdapter, generateCsrf, hashPassword, resetPassword, sealData, signJWT, unsealData, validateCsrf, verifyJWT, verifyPassword, verifyPasswordResetToken };
|
|
81
|
+
export { type CSRFOptions, type IronSession, type JWTOptions, type Password, type PasswordResetOptions, PetaAuthError, type SessionAdapter, type SessionOptions, createPasswordResetToken, createSessionFromAdapter, generateCsrf, hashPassword, resetPassword, sealData, signJWT, unsealData, validateCsrf, verifyJWT, verifyPassword, verifyPasswordResetToken };
|
package/dist/index.mjs
CHANGED
|
@@ -1,31 +1,79 @@
|
|
|
1
|
-
import { n as sealData, r as unsealData
|
|
1
|
+
import { n as sealData, r as unsealData } from "./crypto-WcFV83Nz.mjs";
|
|
2
2
|
import { generateCsrf, validateCsrf } from "./csrf.mjs";
|
|
3
|
+
import { t as PetaAuthError } from "./errors-DxJ-WUJL.mjs";
|
|
3
4
|
import { signJWT, verifyJWT } from "./jwt.mjs";
|
|
4
|
-
import {
|
|
5
|
+
import { t as createSessionFromAdapter } from "./session-BGCQ1Z1Q.mjs";
|
|
6
|
+
import { hash, verify } from "@node-rs/argon2";
|
|
5
7
|
//#region src/password.ts
|
|
8
|
+
const ARGON2_MEMORY_COST = 19456;
|
|
9
|
+
const ARGON2_TIME_COST = 2;
|
|
10
|
+
const ARGON2_PARALLELISM = 1;
|
|
11
|
+
/**
|
|
12
|
+
* Hash a password with argon2id.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const hash = await hashPassword("my-password")
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
6
19
|
async function hashPassword(password, options = {}) {
|
|
7
|
-
return
|
|
20
|
+
return hash(password, {
|
|
21
|
+
algorithm: 2,
|
|
22
|
+
memoryCost: options.memoryCost ?? ARGON2_MEMORY_COST,
|
|
23
|
+
timeCost: options.timeCost ?? ARGON2_TIME_COST,
|
|
24
|
+
parallelism: options.parallelism ?? ARGON2_PARALLELISM
|
|
25
|
+
});
|
|
8
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Verify a password against an argon2id hash.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const ok = await verifyPassword(hash, "my-password")
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
9
35
|
async function verifyPassword(hash, password) {
|
|
10
|
-
|
|
36
|
+
try {
|
|
37
|
+
return await verify(hash, password);
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
11
41
|
}
|
|
12
42
|
//#endregion
|
|
13
43
|
//#region src/reset-password.ts
|
|
14
|
-
const
|
|
44
|
+
const DEFAULT_EXPIRES_IN = 3600;
|
|
45
|
+
/**
|
|
46
|
+
* Create a password-reset token for a user.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const token = await createPasswordResetToken(userId, { password: "..." })
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
15
53
|
async function createPasswordResetToken(userId, options) {
|
|
16
54
|
return signJWT({
|
|
17
55
|
userId,
|
|
18
56
|
purpose: "password-reset"
|
|
19
57
|
}, {
|
|
20
58
|
password: options.password,
|
|
21
|
-
|
|
59
|
+
expiresIn: options.expiresIn ?? DEFAULT_EXPIRES_IN
|
|
22
60
|
});
|
|
23
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Verify a password-reset token.
|
|
64
|
+
*
|
|
65
|
+
* Returns the user ID when the token is valid, or `null` if expired/invalid.
|
|
66
|
+
*/
|
|
24
67
|
async function verifyPasswordResetToken(token, password) {
|
|
25
68
|
const payload = await verifyJWT(token, { password });
|
|
26
|
-
if (
|
|
69
|
+
if (payload?.purpose !== "password-reset") return null;
|
|
27
70
|
return { userId: payload.userId };
|
|
28
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Verify a password-reset token and apply the new password.
|
|
74
|
+
*
|
|
75
|
+
* Returns `{ userId, hash }` on success, or `null` if the token is invalid.
|
|
76
|
+
*/
|
|
29
77
|
async function resetPassword(token, newPassword, password) {
|
|
30
78
|
const payload = await verifyPasswordResetToken(token, password);
|
|
31
79
|
if (!payload) return null;
|
|
@@ -35,4 +83,4 @@ async function resetPassword(token, newPassword, password) {
|
|
|
35
83
|
};
|
|
36
84
|
}
|
|
37
85
|
//#endregion
|
|
38
|
-
export { createPasswordResetToken, createSessionFromAdapter, generateCsrf, hashPassword, resetPassword, sealData, signJWT, unsealData, validateCsrf, verifyJWT, verifyPassword, verifyPasswordResetToken };
|
|
86
|
+
export { PetaAuthError, createPasswordResetToken, createSessionFromAdapter, generateCsrf, hashPassword, resetPassword, sealData, signJWT, unsealData, validateCsrf, verifyJWT, verifyPassword, verifyPasswordResetToken };
|
package/dist/jwt.d.mts
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
|
-
import { t as Password } from "./crypto-
|
|
1
|
+
import { t as Password } from "./crypto-DR-ETdLZ.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/jwt.d.ts
|
|
4
|
+
/** Options for JWT sign / verify operations. */
|
|
4
5
|
interface JWTOptions {
|
|
6
|
+
/** Password used to sign the JWT. */
|
|
5
7
|
password: Password;
|
|
6
|
-
|
|
8
|
+
/** Time-to-live in seconds from now. */
|
|
9
|
+
expiresIn?: number;
|
|
7
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Sign a JWT payload.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const token = await signJWT({ userId: "abc" }, { password: "my-32-char-secret...", expiresIn: 3600 })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
8
19
|
declare function signJWT(payload: Record<string, unknown>, options: JWTOptions): Promise<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Verify and decode a JWT.
|
|
22
|
+
*
|
|
23
|
+
* Returns `null` when the token is invalid or expired.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const payload = await verifyJWT<{ userId: string }>(token, { password: "my-32-char-secret..." })
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
9
30
|
declare function verifyJWT<T = Record<string, unknown>>(token: string, options: JWTOptions): Promise<T | null>;
|
|
10
31
|
//#endregion
|
|
11
32
|
export { JWTOptions, signJWT, verifyJWT };
|
package/dist/jwt.mjs
CHANGED
|
@@ -1,28 +1,48 @@
|
|
|
1
|
+
import { t as normalizePassword } from "./crypto-WcFV83Nz.mjs";
|
|
2
|
+
import { t as PetaAuthError } from "./errors-DxJ-WUJL.mjs";
|
|
1
3
|
import * as jose from "jose";
|
|
2
4
|
//#region src/jwt.ts
|
|
3
|
-
function toPasswordMap(password) {
|
|
4
|
-
return typeof password === "string" ? { 1: password } : password;
|
|
5
|
-
}
|
|
6
5
|
function toKey(secret) {
|
|
7
6
|
return new TextEncoder().encode(secret);
|
|
8
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Sign a JWT payload.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const token = await signJWT({ userId: "abc" }, { password: "my-32-char-secret...", expiresIn: 3600 })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
9
16
|
async function signJWT(payload, options) {
|
|
10
|
-
const map =
|
|
17
|
+
const map = normalizePassword(options.password);
|
|
11
18
|
const secret = map[Math.max(...Object.keys(map).map(Number)).toString()];
|
|
12
|
-
if (!secret || secret.length < 32) throw new
|
|
19
|
+
if (!secret || secret.length < 32) throw new PetaAuthError("JWT_PASSWORD_TOO_SHORT", "peta-auth/jwt: password must be at least 32 characters");
|
|
13
20
|
const jwt = new jose.SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt();
|
|
14
|
-
|
|
21
|
+
const ttl = options.expiresIn ?? 86400;
|
|
22
|
+
jwt.setExpirationTime(Math.floor(Date.now() / 1e3) + ttl);
|
|
15
23
|
return jwt.sign(toKey(secret));
|
|
16
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Verify and decode a JWT.
|
|
27
|
+
*
|
|
28
|
+
* Returns `null` when the token is invalid or expired.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const payload = await verifyJWT<{ userId: string }>(token, { password: "my-32-char-secret..." })
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
17
35
|
async function verifyJWT(token, options) {
|
|
18
|
-
|
|
36
|
+
let result = null;
|
|
37
|
+
const passwords = normalizePassword(options.password);
|
|
38
|
+
for (const secret of Object.values(passwords)) {
|
|
19
39
|
if (!secret) continue;
|
|
20
40
|
try {
|
|
21
41
|
const { payload } = await jose.jwtVerify(token, toKey(secret));
|
|
22
|
-
|
|
42
|
+
if (!result) result = payload;
|
|
23
43
|
} catch {}
|
|
24
44
|
}
|
|
25
|
-
return
|
|
45
|
+
return result;
|
|
26
46
|
}
|
|
27
47
|
//#endregion
|
|
28
48
|
export { signJWT, verifyJWT };
|