@strav/http 0.2.13 → 0.3.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/package.json +5 -3
- package/src/auth/auth.ts +17 -0
- package/src/auth/bridge.ts +154 -0
- package/src/auth/index.ts +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strav/http",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "HTTP layer for the Strav framework — router, server, middleware, authentication, sessions, validation, and views",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,12 +32,14 @@
|
|
|
32
32
|
"./policy/*": "./src/policy/*.ts",
|
|
33
33
|
"./auth": "./src/auth/index.ts",
|
|
34
34
|
"./auth/*": "./src/auth/*.ts",
|
|
35
|
+
"./auth/bridge": "./src/auth/bridge.ts",
|
|
35
36
|
"./providers": "./src/providers/index.ts",
|
|
36
37
|
"./providers/*": "./src/providers/*.ts"
|
|
37
38
|
},
|
|
38
39
|
"peerDependencies": {
|
|
39
|
-
"@strav/kernel": "0.
|
|
40
|
-
"@strav/database": "0.
|
|
40
|
+
"@strav/kernel": "0.3.0",
|
|
41
|
+
"@strav/database": "0.3.0",
|
|
42
|
+
"@strav/auth": "0.3.0"
|
|
41
43
|
},
|
|
42
44
|
"dependencies": {
|
|
43
45
|
"@vue/compiler-sfc": "^3.5.28",
|
package/src/auth/auth.ts
CHANGED
|
@@ -7,6 +7,23 @@ import Database from '@strav/database/database/database'
|
|
|
7
7
|
export { extractUserId } from '@strav/database/helpers/identity'
|
|
8
8
|
export { randomHex } from '@strav/kernel/helpers/crypto'
|
|
9
9
|
|
|
10
|
+
// Re-export commonly used auth primitives for convenience
|
|
11
|
+
export {
|
|
12
|
+
signJWT,
|
|
13
|
+
verifyJWT,
|
|
14
|
+
createAccessToken as createJWTAccessToken,
|
|
15
|
+
createRefreshToken as createJWTRefreshToken,
|
|
16
|
+
verifyAccessToken,
|
|
17
|
+
verifyRefreshToken
|
|
18
|
+
} from '@strav/auth/jwt'
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
createSignedToken,
|
|
22
|
+
verifySignedToken,
|
|
23
|
+
createMagicLinkToken,
|
|
24
|
+
verifyMagicLinkToken
|
|
25
|
+
} from '@strav/auth/tokens'
|
|
26
|
+
|
|
10
27
|
export interface TokenConfig {
|
|
11
28
|
expiration: number | null
|
|
12
29
|
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type Context from '../http/context.ts'
|
|
2
|
+
import { signJWT, verifyJWT, createMagicLinkToken, verifySignedToken } from '@strav/auth'
|
|
3
|
+
import type { JWTPayload, JWTSignOptions } from '@strav/auth'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Helper functions to bridge @strav/auth primitives with HTTP contexts.
|
|
7
|
+
* These utilities make it easy to use low-level auth tokens in HTTP scenarios.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a JWT token and return a Set-Cookie header value.
|
|
12
|
+
*
|
|
13
|
+
* @param payload - JWT payload
|
|
14
|
+
* @param secret - Secret for signing
|
|
15
|
+
* @param options - JWT and cookie options
|
|
16
|
+
* @returns Object containing the JWT token and Set-Cookie header value
|
|
17
|
+
*/
|
|
18
|
+
export async function createJWTCookie(
|
|
19
|
+
payload: JWTPayload,
|
|
20
|
+
secret: string | Uint8Array,
|
|
21
|
+
options: {
|
|
22
|
+
cookieName?: string
|
|
23
|
+
cookieOptions?: {
|
|
24
|
+
domain?: string
|
|
25
|
+
path?: string
|
|
26
|
+
secure?: boolean
|
|
27
|
+
sameSite?: 'strict' | 'lax' | 'none'
|
|
28
|
+
maxAge?: number
|
|
29
|
+
}
|
|
30
|
+
} & JWTSignOptions = {}
|
|
31
|
+
): Promise<{ token: string; cookieHeader: string }> {
|
|
32
|
+
const { cookieName = 'auth-token', cookieOptions = {}, ...jwtOptions } = options
|
|
33
|
+
|
|
34
|
+
const token = await signJWT(payload, secret, jwtOptions)
|
|
35
|
+
|
|
36
|
+
const cookieParts = [
|
|
37
|
+
`${cookieName}=${token}`,
|
|
38
|
+
'HttpOnly',
|
|
39
|
+
'Secure',
|
|
40
|
+
'SameSite=Strict'
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
if (cookieOptions.domain) cookieParts.push(`Domain=${cookieOptions.domain}`)
|
|
44
|
+
if (cookieOptions.path) cookieParts.push(`Path=${cookieOptions.path}`)
|
|
45
|
+
if (cookieOptions.maxAge) cookieParts.push(`Max-Age=${cookieOptions.maxAge}`)
|
|
46
|
+
if (cookieOptions.secure === false) cookieParts.splice(cookieParts.indexOf('Secure'), 1)
|
|
47
|
+
if (cookieOptions.sameSite) {
|
|
48
|
+
const index = cookieParts.findIndex(p => p.startsWith('SameSite'))
|
|
49
|
+
cookieParts[index] = `SameSite=${cookieOptions.sameSite}`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
token,
|
|
54
|
+
cookieHeader: cookieParts.join('; ')
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Extract and verify a JWT token from an HTTP-only cookie.
|
|
60
|
+
*
|
|
61
|
+
* @param ctx - The HTTP context
|
|
62
|
+
* @param secret - Secret for verification
|
|
63
|
+
* @param cookieName - Name of the cookie (defaults to 'auth-token')
|
|
64
|
+
* @returns The verified JWT payload or null if invalid
|
|
65
|
+
*/
|
|
66
|
+
export async function verifyJWTCookie<T = JWTPayload>(
|
|
67
|
+
ctx: Context,
|
|
68
|
+
secret: string | Uint8Array,
|
|
69
|
+
cookieName: string = 'auth-token'
|
|
70
|
+
): Promise<T | null> {
|
|
71
|
+
const token = ctx.cookie(cookieName)
|
|
72
|
+
if (!token) return null
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
return await verifyJWT(token, secret) as T
|
|
76
|
+
} catch {
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a magic link URL with an embedded signed token.
|
|
83
|
+
*
|
|
84
|
+
* @param baseUrl - Base URL for the magic link
|
|
85
|
+
* @param userId - User identifier
|
|
86
|
+
* @param options - Magic link options
|
|
87
|
+
* @returns Complete magic link URL
|
|
88
|
+
*/
|
|
89
|
+
export function createMagicLinkURL(
|
|
90
|
+
baseUrl: string,
|
|
91
|
+
userId: string | number,
|
|
92
|
+
options: {
|
|
93
|
+
email?: string
|
|
94
|
+
redirect?: string
|
|
95
|
+
expiresInMinutes?: number
|
|
96
|
+
tokenParam?: string
|
|
97
|
+
} = {}
|
|
98
|
+
): string {
|
|
99
|
+
const { tokenParam = 'token', ...tokenOptions } = options
|
|
100
|
+
|
|
101
|
+
const token = createMagicLinkToken(userId, tokenOptions)
|
|
102
|
+
const url = new URL(baseUrl)
|
|
103
|
+
url.searchParams.set(tokenParam, token)
|
|
104
|
+
|
|
105
|
+
if (options.redirect) {
|
|
106
|
+
url.searchParams.set('redirect', options.redirect)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return url.toString()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Extract and verify a magic link token from HTTP context.
|
|
114
|
+
*
|
|
115
|
+
* @param ctx - The HTTP context
|
|
116
|
+
* @param tokenParam - URL parameter containing the token (defaults to 'token')
|
|
117
|
+
* @returns The verified token payload or null if invalid
|
|
118
|
+
*/
|
|
119
|
+
export function verifyMagicLinkFromContext(
|
|
120
|
+
ctx: Context,
|
|
121
|
+
tokenParam: string = 'token'
|
|
122
|
+
): { sub: string | number; email?: string; redirect?: string } | null {
|
|
123
|
+
const token = ctx.query.get(tokenParam)
|
|
124
|
+
if (!token) return null
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
return verifySignedToken(token)
|
|
128
|
+
} catch {
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Extract Bearer token from Authorization header.
|
|
135
|
+
*
|
|
136
|
+
* @param ctx - The HTTP context
|
|
137
|
+
* @returns The token string or null if not present
|
|
138
|
+
*/
|
|
139
|
+
export function extractBearerToken(ctx: Context): string | null {
|
|
140
|
+
const header = ctx.header('authorization')
|
|
141
|
+
if (!header?.startsWith('Bearer ')) return null
|
|
142
|
+
|
|
143
|
+
return header.slice(7)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Create an Authorization Bearer token header value.
|
|
148
|
+
*
|
|
149
|
+
* @param token - The token to set
|
|
150
|
+
* @returns The formatted Authorization header value
|
|
151
|
+
*/
|
|
152
|
+
export function createBearerTokenHeader(token: string): string {
|
|
153
|
+
return `Bearer ${token}`
|
|
154
|
+
}
|
package/src/auth/index.ts
CHANGED
|
@@ -5,3 +5,13 @@ export { csrf } from './middleware/csrf.ts'
|
|
|
5
5
|
export { guest } from './middleware/guest.ts'
|
|
6
6
|
export type { AuthConfig, TokenConfig } from './auth.ts'
|
|
7
7
|
export type { AccessTokenData } from './access_token.ts'
|
|
8
|
+
|
|
9
|
+
// HTTP-specific auth bridge helpers
|
|
10
|
+
export * from './bridge.ts'
|
|
11
|
+
|
|
12
|
+
// Re-export low-level auth primitives from @strav/auth
|
|
13
|
+
export * from '@strav/auth/jwt'
|
|
14
|
+
export * from '@strav/auth/tokens'
|
|
15
|
+
export * from '@strav/auth/totp'
|
|
16
|
+
export * from '@strav/auth/oauth'
|
|
17
|
+
export * from '@strav/auth/validation'
|