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 +42 -4
- package/dist/elysia.d.mts +2 -30
- package/dist/elysia.mjs +2 -2
- package/dist/hono.d.mts +1 -0
- package/dist/hono.mjs +2 -2
- package/dist/index.d.mts +15 -1
- package/dist/index.mjs +26 -1
- package/dist/nuxt.d.mts +1 -0
- package/dist/nuxt.mjs +2 -2
- package/package.json +1 -1
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.
|
|
160
|
+
Returns 401 if the session has no user data. Optionally checks a specific session key:
|
|
161
161
|
|
|
162
|
-
|
|
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 #
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
});
|