peta-auth 0.1.2 → 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/README.md CHANGED
@@ -157,11 +157,21 @@ Without a generic parameter, session data defaults to `Record<string, unknown>`.
157
157
 
158
158
  ### `requireSession()` guard
159
159
 
160
- Returns 401 if the session has no user data. Works per-framework:
160
+ Returns 401 if the session has no user data. Optionally checks a specific session key:
161
161
 
162
- - **Hono**: `app.use('/protected/*', requireSession())` — middleware, path-patterned
162
+ ```ts
163
+ // Guard on any session data
164
+ app.use('/api/*', requireSession())
165
+
166
+ // Guard on a specific key (e.g. session.userId must be truthy)
167
+ app.use('/admin/*', requireSession('userId'))
168
+ ```
169
+
170
+ Works per-framework:
171
+
172
+ - **Hono**: `app.use('/protected/*', requireSession())` — path-patterned middleware
163
173
  - **Elysia**: `app.use(requireSession())` — guards all routes defined after it
164
- - **Nuxt**: `requireSession(event, session)` — throws `createError({ statusCode: 401 })`
174
+ - **Nuxt**: `requireSession(event, session)` or `requireSession(event, session, 'userId')` — throws `createError({ statusCode: 401 })`
165
175
 
166
176
  ### Session object
167
177
 
@@ -354,7 +364,7 @@ Session data is serialized, encrypted with AES-256-CBC, integrity-protected with
354
364
  ## Scripts
355
365
 
356
366
  ```bash
357
- bun test # 65 tests across 12 files
367
+ bun test # 74 tests across 12 files
358
368
  bun run build # tsdown → dist/ (21 files, 30 kB)
359
369
  bun run prepublish # build + publish
360
370
  ```
@@ -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-z20gaFVT.mjs";
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 === token;
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-z20gaFVT.mjs";
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,35 +43,19 @@ declare function session<T extends Record<string, unknown> = Record<string, unkn
34
43
  standaloneSchema: {};
35
44
  response: {};
36
45
  }>;
37
- declare function requireSession(): (app: Elysia) => Elysia<"", {
38
- decorator: {};
39
- store: {};
40
- derive: {};
41
- resolve: {};
42
- }, {
43
- typebox: {};
44
- error: {};
45
- }, {
46
- schema: {};
47
- standaloneSchema: {};
48
- macro: {};
49
- macroFn: {};
50
- parser: {};
51
- response: {};
52
- }, {}, {
53
- derive: {};
54
- resolve: {};
55
- schema: {};
56
- standaloneSchema: {};
57
- response: {};
58
- }, {
59
- derive: {};
60
- resolve: {};
61
- schema: {};
62
- standaloneSchema: {};
63
- response: {
64
- 200: Response;
65
- };
66
- }>;
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
+ */
58
+ declare function requireSession(): (app: Elysia) => Elysia;
59
+ declare function requireSession<K extends string>(key: K): (app: Elysia) => Elysia;
67
60
  //#endregion
68
61
  export { requireSession, session };
package/dist/elysia.mjs CHANGED
@@ -1,22 +1,31 @@
1
- import { t as createSessionFromAdapter } from "./session-DSwf3XPH.mjs";
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: reqHeaders, set }) => {
7
- const cookieStr = reqHeaders instanceof Headers ? reqHeaders.get("cookie") ?? "" : reqHeaders.cookie ?? "";
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(cookieStr)[name],
10
- setCookie: (v) => {
11
- set.headers["Set-Cookie"] = v;
18
+ getCookie: (name) => parse(cookieString)[name],
19
+ setCookie: (value) => {
20
+ set.headers["Set-Cookie"] = value;
12
21
  }
13
22
  }, options) };
14
23
  });
15
24
  }
16
- function requireSession() {
25
+ function requireSession(key) {
17
26
  return (app) => app.onBeforeHandle((context) => {
18
27
  const session = context.session;
19
- if (!Object.keys(session).some((k) => k !== "save" && k !== "destroy" && k !== "updateConfig")) return new Response(JSON.stringify({ error: "unauthorized" }), {
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,34 @@
1
- import { r as SessionOptions, t as IronSession } from "./session-z20gaFVT.mjs";
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;
32
+ declare function requireSession<K extends string>(key: K): MiddlewareHandler;
11
33
  //#endregion
12
34
  export { requireSession, session };
package/dist/hono.mjs CHANGED
@@ -1,20 +1,30 @@
1
- import { t as createSessionFromAdapter } from "./session-DSwf3XPH.mjs";
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: (v) => c.res.headers.append("Set-Cookie", v)
19
+ setCookie: (value) => c.res.headers.append("Set-Cookie", value)
10
20
  }, options));
11
21
  await next();
12
22
  });
13
23
  }
14
- function requireSession() {
24
+ function requireSession(key) {
15
25
  return createMiddleware(async (c, next) => {
16
26
  const s = c.var.session;
17
- if (!Object.keys(s).some((k) => k !== "save" && k !== "destroy" && k !== "updateConfig")) return c.json({ error: "unauthorized" }, 401);
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-Ln_Mj_zp.mjs";
2
- import { i as createSessionFromAdapter, n as SessionAdapter, r as SessionOptions, t as IronSession } from "./session-z20gaFVT.mjs";
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?: number;
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
- exp?: number;
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, t as createSessionFromAdapter } from "./session-DSwf3XPH.mjs";
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 { compareSync, genSaltSync, hashSync } from "bcryptjs";
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 hashSync(password, genSaltSync(options.cost ?? 10));
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
- return compareSync(password, hash);
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 DEFAULT_EXPIRY = 3600;
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
- exp: options.exp ?? DEFAULT_EXPIRY
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 (!payload || payload.purpose !== "password-reset") return null;
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-Ln_Mj_zp.mjs";
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
- exp?: number;
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 };