peta-auth 0.1.1 → 0.1.3

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
 
@@ -226,6 +236,33 @@ if (!validateCsrf(session, body._csrf)) {
226
236
 
227
237
  ---
228
238
 
239
+ ## Password Reset
240
+
241
+ Helpers for forgot/reset password flows using short-lived JWTs.
242
+
243
+ ```ts
244
+ import { createPasswordResetToken, verifyPasswordResetToken, resetPassword } from 'peta-auth'
245
+
246
+ // Generate a token (e.g., in a "forgot password" endpoint)
247
+ const token = await createPasswordResetToken(user.email, {
248
+ password: process.env.SECRET!,
249
+ exp: 3600, // optional, default 1 hour
250
+ })
251
+ // → email token as a link: https://example.com/reset?token=${token}
252
+
253
+ // Verify a token (e.g., in a "reset password" endpoint)
254
+ const payload = await verifyPasswordResetToken(token, process.env.SECRET!)
255
+ if (!payload) throw new Error('Invalid or expired token')
256
+ // payload.userId → the email passed to createPasswordResetToken
257
+
258
+ // Combined: verify token + hash new password
259
+ const result = await resetPassword(token, newPassword, process.env.SECRET!)
260
+ if (!result) throw new Error('Invalid or expired token')
261
+ users.set(result.userId, { ...user, hash: result.hash })
262
+ ```
263
+
264
+ ---
265
+
229
266
  ## Low-level
230
267
 
231
268
  ```ts
@@ -300,6 +337,7 @@ bun run examples/hono-guard.ts # Hono — requireSession guard
300
337
  bun run examples/elysia-basic.ts # Elysia — session CRUD, views counter
301
338
  bun run examples/elysia-guard.ts # Elysia — requireSession guard
302
339
  bun run examples/password-auth.ts # Hono — signup + login with bcrypt
340
+ bun run examples/password-reset.ts # Hono — forgot/reset password flow
303
341
  bun run examples/jwt-basic.ts # JWT sign + verify + tamper detection
304
342
  bun run examples/csrf-basic.ts # Hono — CSRF token form example
305
343
  bun run examples/oauth-github.ts # Hono — GitHub OAuth
@@ -326,7 +364,7 @@ Session data is serialized, encrypted with AES-256-CBC, integrity-protected with
326
364
  ## Scripts
327
365
 
328
366
  ```bash
329
- bun test # 60 tests across 11 files
367
+ bun test # 74 tests across 12 files
330
368
  bun run build # tsdown → dist/ (21 files, 30 kB)
331
369
  bun run prepublish # build + publish
332
370
  ```
package/dist/elysia.d.mts CHANGED
@@ -34,35 +34,7 @@ declare function session<T extends Record<string, unknown> = Record<string, unkn
34
34
  standaloneSchema: {};
35
35
  response: {};
36
36
  }>;
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
- }>;
37
+ declare function requireSession(): (app: Elysia) => Elysia;
38
+ declare function requireSession<K extends string>(key: K): (app: Elysia) => Elysia;
67
39
  //#endregion
68
40
  export { requireSession, session };
package/dist/elysia.mjs CHANGED
@@ -13,10 +13,10 @@ function session(options) {
13
13
  }, options) };
14
14
  });
15
15
  }
16
- function requireSession() {
16
+ function requireSession(key) {
17
17
  return (app) => app.onBeforeHandle((context) => {
18
18
  const session = context.session;
19
- if (!Object.keys(session).some((k) => k !== "save" && k !== "destroy" && k !== "updateConfig")) return new Response(JSON.stringify({ error: "unauthorized" }), {
19
+ if (!(key ? !!session[key] : Object.keys(session).some((k) => k !== "save" && k !== "destroy" && k !== "updateConfig"))) return new Response(JSON.stringify({ error: "unauthorized" }), {
20
20
  status: 401,
21
21
  headers: { "Content-Type": "application/json" }
22
22
  });
package/dist/hono.d.mts CHANGED
@@ -8,5 +8,6 @@ declare function session<T extends Record<string, unknown> = Record<string, unkn
8
8
  };
9
9
  }>;
10
10
  declare function requireSession(): MiddlewareHandler;
11
+ declare function requireSession<K extends string>(key: K): MiddlewareHandler;
11
12
  //#endregion
12
13
  export { requireSession, session };
package/dist/hono.mjs CHANGED
@@ -11,10 +11,10 @@ function session(options) {
11
11
  await next();
12
12
  });
13
13
  }
14
- function requireSession() {
14
+ function requireSession(key) {
15
15
  return createMiddleware(async (c, next) => {
16
16
  const s = c.var.session;
17
- if (!Object.keys(s).some((k) => k !== "save" && k !== "destroy" && k !== "updateConfig")) return c.json({ error: "unauthorized" }, 401);
17
+ if (!(key ? !!s[key] : Object.keys(s).some((k) => k !== "save" && k !== "destroy" && k !== "updateConfig"))) return c.json({ error: "unauthorized" }, 401);
18
18
  await next();
19
19
  });
20
20
  }
package/dist/index.d.mts CHANGED
@@ -10,4 +10,18 @@ interface HashOptions {
10
10
  declare function hashPassword(password: string, options?: HashOptions): Promise<string>;
11
11
  declare function verifyPassword(hash: string, password: string): Promise<boolean>;
12
12
  //#endregion
13
- export { type CSRFOptions, type IronSession, type JWTOptions, type Password, type SessionAdapter, type SessionOptions, createSessionFromAdapter, generateCsrf, hashPassword, sealData, signJWT, unsealData, validateCsrf, verifyJWT, verifyPassword };
13
+ //#region src/reset-password.d.ts
14
+ interface PasswordResetOptions {
15
+ password: Password;
16
+ exp?: number;
17
+ }
18
+ declare function createPasswordResetToken(userId: string, options: PasswordResetOptions): Promise<string>;
19
+ declare function verifyPasswordResetToken(token: string, password: Password): Promise<{
20
+ userId: string;
21
+ } | null>;
22
+ declare function resetPassword(token: string, newPassword: string, password: Password): Promise<{
23
+ userId: string;
24
+ hash: string;
25
+ } | null>;
26
+ //#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 };
package/dist/index.mjs CHANGED
@@ -10,4 +10,29 @@ async function verifyPassword(hash, password) {
10
10
  return compareSync(password, hash);
11
11
  }
12
12
  //#endregion
13
- export { createSessionFromAdapter, generateCsrf, hashPassword, sealData, signJWT, unsealData, validateCsrf, verifyJWT, verifyPassword };
13
+ //#region src/reset-password.ts
14
+ const DEFAULT_EXPIRY = 3600;
15
+ async function createPasswordResetToken(userId, options) {
16
+ return signJWT({
17
+ userId,
18
+ purpose: "password-reset"
19
+ }, {
20
+ password: options.password,
21
+ exp: options.exp ?? DEFAULT_EXPIRY
22
+ });
23
+ }
24
+ async function verifyPasswordResetToken(token, password) {
25
+ const payload = await verifyJWT(token, { password });
26
+ if (!payload || payload.purpose !== "password-reset") return null;
27
+ return { userId: payload.userId };
28
+ }
29
+ async function resetPassword(token, newPassword, password) {
30
+ const payload = await verifyPasswordResetToken(token, password);
31
+ if (!payload) return null;
32
+ return {
33
+ userId: payload.userId,
34
+ hash: await hashPassword(newPassword)
35
+ };
36
+ }
37
+ //#endregion
38
+ export { createPasswordResetToken, createSessionFromAdapter, generateCsrf, hashPassword, resetPassword, sealData, signJWT, unsealData, validateCsrf, verifyJWT, verifyPassword, verifyPasswordResetToken };
package/dist/nuxt.d.mts CHANGED
@@ -4,5 +4,6 @@ import { H3Event } from "h3";
4
4
  //#region src/nuxt.d.ts
5
5
  declare function useSession<T extends Record<string, unknown> = Record<string, unknown>>(event: H3Event, options: SessionOptions): Promise<T & IronSession>;
6
6
  declare function requireSession(_event: H3Event, session: IronSession): void;
7
+ declare function requireSession<K extends string>(_event: H3Event, session: IronSession, key: K): void;
7
8
  //#endregion
8
9
  export { requireSession, useSession };
package/dist/nuxt.mjs CHANGED
@@ -14,8 +14,8 @@ function useSession(event, options) {
14
14
  cookieOptions: options?.cookieOptions
15
15
  });
16
16
  }
17
- function requireSession(_event, session) {
18
- if (!Object.keys(session).some((k) => k !== "save" && k !== "destroy" && k !== "updateConfig")) throw createError({
17
+ function requireSession(_event, session, key) {
18
+ if (!(key ? !!session[key] : Object.keys(session).some((k) => k !== "save" && k !== "destroy" && k !== "updateConfig"))) throw createError({
19
19
  statusCode: 401,
20
20
  statusMessage: "unauthorized"
21
21
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peta-auth",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Encrypted cookie sessions for Bun — Hono, ElysiaJS & Nuxt adapters",
5
5
  "type": "module",
6
6
  "module": "./dist/index.mjs",