@a-type/auth
Advanced tools
Comparing version
import { RETURN_TO_COOKIE } from './returnTo.js'; | ||
import { getOrCreateSession } from './session.js'; | ||
import * as z from 'zod'; | ||
export function createHandlers({ providers, db, defaultReturnTo = '/', email: emailService, createSession, }) { | ||
export function createHandlers({ providers, db, defaultReturnTo = '/', email: emailService, sessions, }) { | ||
const supportsEmail = !!db.insertVerificationCode && | ||
@@ -76,13 +75,8 @@ !!db.getUserByEmailAndPassword && | ||
} | ||
const res = new Response(null, { | ||
const session = await sessions.createSession(userId); | ||
const sessionHeaders = await sessions.updateSession(session); | ||
return new Response(null, { | ||
status: 302, | ||
headers: { | ||
location: (_d = url.searchParams.get('returnTo')) !== null && _d !== void 0 ? _d : defaultReturnTo, | ||
}, | ||
headers: Object.assign(Object.assign({}, sessionHeaders), { location: (_d = url.searchParams.get('returnTo')) !== null && _d !== void 0 ? _d : defaultReturnTo }), | ||
}); | ||
const session = await getOrCreateSession(req, res); | ||
const sessionDetails = await createSession(userId); | ||
Object.assign(session, sessionDetails); | ||
await session.save(); | ||
return res; | ||
} | ||
@@ -93,11 +87,6 @@ async function handleLogoutRequest(req) { | ||
const returnTo = (_a = url.searchParams.get('returnTo')) !== null && _a !== void 0 ? _a : defaultReturnTo; | ||
const res = new Response(null, { | ||
return new Response(null, { | ||
status: 302, | ||
headers: { | ||
location: returnTo, | ||
}, | ||
headers: Object.assign(Object.assign({}, sessions.clearSession()), { location: returnTo }), | ||
}); | ||
const session = await getOrCreateSession(req, res); | ||
session.destroy(); | ||
return res; | ||
} | ||
@@ -131,2 +120,8 @@ async function handleSendEmailVerificationRequest(req) { | ||
})); | ||
return new Response(JSON.stringify({ ok: true }), { | ||
status: 200, | ||
headers: { | ||
'content-type': 'application/json', | ||
}, | ||
}); | ||
} | ||
@@ -170,13 +165,7 @@ async function handleVerifyEmailRequest(req) { | ||
})); | ||
const res = new Response(null, { | ||
const session = await sessions.createSession(user.id); | ||
return new Response(null, { | ||
status: 302, | ||
headers: { | ||
location: (_c = url.searchParams.get('returnTo')) !== null && _c !== void 0 ? _c : defaultReturnTo, | ||
}, | ||
headers: Object.assign(Object.assign({}, (await sessions.updateSession(session))), { location: (_c = url.searchParams.get('returnTo')) !== null && _c !== void 0 ? _c : defaultReturnTo }), | ||
}); | ||
const session = await getOrCreateSession(req, res); | ||
const sessionDetails = createSession(user.id); | ||
Object.assign(session, sessionDetails); | ||
await session.save(); | ||
return res; | ||
} | ||
@@ -200,13 +189,7 @@ async function handleEmailLoginRequest(req) { | ||
} | ||
const res = new Response(null, { | ||
const session = await sessions.createSession(user.id); | ||
return new Response(null, { | ||
status: 302, | ||
headers: { | ||
location: (_b = params.returnTo) !== null && _b !== void 0 ? _b : defaultReturnTo, | ||
}, | ||
headers: Object.assign(Object.assign({}, (await sessions.updateSession(session))), { location: (_b = params.returnTo) !== null && _b !== void 0 ? _b : defaultReturnTo }), | ||
}); | ||
const session = await getOrCreateSession(req, res); | ||
const sessionDetails = await createSession(user.id); | ||
Object.assign(session, sessionDetails); | ||
await session.save(); | ||
return res; | ||
} | ||
@@ -258,13 +241,7 @@ async function handleResetPasswordRequest(req) { | ||
} | ||
const res = new Response(null, { | ||
const session = await sessions.createSession(user.id); | ||
return new Response(null, { | ||
status: 302, | ||
headers: { | ||
location: (_b = url.searchParams.get('returnTo')) !== null && _b !== void 0 ? _b : defaultReturnTo, | ||
}, | ||
headers: Object.assign(Object.assign({}, (await sessions.updateSession(session))), { location: (_b = url.searchParams.get('returnTo')) !== null && _b !== void 0 ? _b : defaultReturnTo }), | ||
}); | ||
const session = await getOrCreateSession(req, res); | ||
const sessionDetails = await createSession(user.id); | ||
Object.assign(session, sessionDetails); | ||
await session.save(); | ||
return res; | ||
} | ||
@@ -271,0 +248,0 @@ return { |
@@ -1,20 +0,77 @@ | ||
import { getIronSession } from 'iron-session'; | ||
const SESSION_COOKIE = 'lg-session'; | ||
const SESSION_SECRET = process.env.SESSION_SECRET; | ||
if (!SESSION_SECRET) { | ||
throw new Error('SESSION_SECRET must be set'); | ||
} | ||
export async function getLiveSession(req, res) { | ||
const rawSession = await getOrCreateSession(req, res); | ||
if (!rawSession.userId) { | ||
return null; | ||
import { SignJWT, jwtVerify } from 'jose'; | ||
export const defaultShortNames = { | ||
userId: 'sub', | ||
}; | ||
export class SessionManager { | ||
constructor(options) { | ||
this.options = options; | ||
this.createSession = async (userId) => { | ||
return this.options.createSession(userId); | ||
}; | ||
this.getSession = async (req) => { | ||
var _a; | ||
const cookie = (_a = req.headers.get('cookie')) !== null && _a !== void 0 ? _a : ''; | ||
const match = cookie.match(new RegExp(`${this.options.cookieName}=([^;]+)`)); | ||
if (!match) { | ||
return null; | ||
} | ||
const cookieValue = match[1]; | ||
// read the JWT from the cookie | ||
const jwt = await jwtVerify(cookieValue, this.secret); | ||
// convert the JWT claims to a session object | ||
const session = Object.fromEntries(Object.entries(jwt).map(([key, value]) => [this.getLongName(key), value])); | ||
// in dev mode, validate session has the right keys | ||
if (this.options.mode === 'development') { | ||
const keys = Object.keys(session); | ||
const expectedKeys = Object.keys(this.options.shortNames); | ||
if (keys.length !== expectedKeys.length) { | ||
throw new Error('Session has the wrong number of keys'); | ||
} | ||
for (const key of keys) { | ||
if (!expectedKeys.includes(key)) { | ||
throw new Error(`Session has unexpected key: ${key}`); | ||
} | ||
} | ||
} | ||
return session; | ||
}; | ||
this.updateSession = async (session) => { | ||
const builder = new SignJWT(Object.fromEntries(Object.entries(session).map(([key, value]) => [ | ||
this.getShortName(key), | ||
value, | ||
]))) | ||
.setProtectedHeader({ alg: 'HS256' }) | ||
.setIssuedAt() | ||
.setExpirationTime('1h'); | ||
if (this.options.issuer) { | ||
builder.setIssuer(this.options.issuer); | ||
} | ||
if (this.options.audience) { | ||
builder.setAudience(this.options.audience); | ||
} | ||
const jwt = builder.sign(this.secret); | ||
return { | ||
'Set-Cookie': `${this.options.cookieName}=${jwt}; Path=/; HttpOnly; SameSite=Strict`, | ||
}; | ||
}; | ||
this.clearSession = () => { | ||
return { | ||
'Set-Cookie': `${this.options.cookieName}=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0`, | ||
}; | ||
}; | ||
this.getShortName = (key) => { | ||
return this.options.shortNames[key]; | ||
}; | ||
this.getLongName = (shortName) => { | ||
return this.shortNamesBackwards[shortName]; | ||
}; | ||
this.secret = new TextEncoder().encode(options.secret); | ||
this.shortNamesBackwards = Object.fromEntries(Object.entries(options.shortNames).map(([key, value]) => [value, key])); | ||
// validate shortnames don't repeat | ||
const values = Object.values(options.shortNames); | ||
if (new Set(values).size !== values.length) { | ||
throw new Error('Short names must be unique'); | ||
} | ||
} | ||
return rawSession; | ||
} | ||
export function getOrCreateSession(req, res) { | ||
return getIronSession(req, res, { | ||
cookieName: SESSION_COOKIE, | ||
password: SESSION_SECRET, | ||
}); | ||
} | ||
//# sourceMappingURL=session.js.map |
import { AuthDB } from './db.js'; | ||
import { Email } from './email.js'; | ||
import { AuthProvider } from './providers/types.js'; | ||
import { Session } from './session.js'; | ||
export declare function createHandlers({ providers, db, defaultReturnTo, email: emailService, createSession, }: { | ||
import { SessionManager } from './session.js'; | ||
export declare function createHandlers({ providers, db, defaultReturnTo, email: emailService, sessions, }: { | ||
providers: Record<string, AuthProvider>; | ||
@@ -10,3 +10,3 @@ db: AuthDB; | ||
email?: Email; | ||
createSession: (userId: string) => Promise<Session>; | ||
sessions: SessionManager; | ||
}): { | ||
@@ -20,3 +20,3 @@ handleOAuthLoginRequest: (req: Request, opts: { | ||
handleLogoutRequest: (req: Request) => Promise<Response>; | ||
handleSendEmailVerificationRequest: (req: Request) => Promise<void>; | ||
handleSendEmailVerificationRequest: (req: Request) => Promise<Response>; | ||
handleVerifyEmailRequest: (req: Request) => Promise<Response>; | ||
@@ -23,0 +23,0 @@ handleEmailLoginRequest: (req: Request) => Promise<Response>; |
export interface Session { | ||
userId: string; | ||
} | ||
export declare function getLiveSession(req: Request, res: Response): Promise<Session | null>; | ||
export declare function getOrCreateSession(req: Request, res: Response): Promise<import("iron-session").IronSession<Partial<Session>>>; | ||
export type ShortNames = { | ||
[key in keyof Session]: string; | ||
}; | ||
export declare const defaultShortNames: { | ||
userId: string; | ||
}; | ||
export declare class SessionManager { | ||
private options; | ||
private secret; | ||
private shortNamesBackwards; | ||
constructor(options: { | ||
secret: string; | ||
cookieName: string; | ||
shortNames: ShortNames; | ||
mode?: 'production' | 'development'; | ||
createSession: (userId: string) => Promise<Session>; | ||
issuer?: string; | ||
audience?: string; | ||
}); | ||
createSession: (userId: string) => Promise<Session>; | ||
getSession: (req: Request) => Promise<Session | null>; | ||
updateSession: (session: Session) => Promise<HeadersInit>; | ||
clearSession: () => HeadersInit; | ||
private getShortName; | ||
private getLongName; | ||
} |
{ | ||
"name": "@a-type/auth", | ||
"version": "0.1.4", | ||
"version": "0.2.0", | ||
"description": "My personal auth request handlers", | ||
@@ -20,3 +20,3 @@ "module": "dist/esm/index.js", | ||
"googleapis": "^129.0.0", | ||
"iron-session": "^8.0.1", | ||
"jose": "^5.2.2", | ||
"nodemailer": "^6.9.8", | ||
@@ -23,0 +23,0 @@ "zod": "^3.22.4" |
@@ -5,3 +5,3 @@ import { AuthDB } from './db.js'; | ||
import { RETURN_TO_COOKIE } from './returnTo.js'; | ||
import { Session, getOrCreateSession } from './session.js'; | ||
import { SessionManager } from './session.js'; | ||
import * as z from 'zod'; | ||
@@ -14,3 +14,3 @@ | ||
email: emailService, | ||
createSession, | ||
sessions, | ||
}: { | ||
@@ -21,3 +21,3 @@ providers: Record<string, AuthProvider>; | ||
email?: Email; | ||
createSession: (userId: string) => Promise<Session>; | ||
sessions: SessionManager; | ||
}) { | ||
@@ -109,13 +109,11 @@ const supportsEmail = | ||
const res = new Response(null, { | ||
const session = await sessions.createSession(userId); | ||
const sessionHeaders = await sessions.updateSession(session); | ||
return new Response(null, { | ||
status: 302, | ||
headers: { | ||
...sessionHeaders, | ||
location: url.searchParams.get('returnTo') ?? defaultReturnTo, | ||
}, | ||
}); | ||
const session = await getOrCreateSession(req, res); | ||
const sessionDetails = await createSession(userId); | ||
Object.assign(session, sessionDetails); | ||
await session.save(); | ||
return res; | ||
} | ||
@@ -126,11 +124,9 @@ | ||
const returnTo = url.searchParams.get('returnTo') ?? defaultReturnTo; | ||
const res = new Response(null, { | ||
return new Response(null, { | ||
status: 302, | ||
headers: { | ||
...sessions.clearSession(), | ||
location: returnTo, | ||
}, | ||
}); | ||
const session = await getOrCreateSession(req, res); | ||
session.destroy(); | ||
return res; | ||
} | ||
@@ -167,2 +163,9 @@ | ||
}); | ||
return new Response(JSON.stringify({ ok: true }), { | ||
status: 200, | ||
headers: { | ||
'content-type': 'application/json', | ||
}, | ||
}); | ||
} | ||
@@ -206,13 +209,10 @@ | ||
}); | ||
const res = new Response(null, { | ||
const session = await sessions.createSession(user.id); | ||
return new Response(null, { | ||
status: 302, | ||
headers: { | ||
...(await sessions.updateSession(session)), | ||
location: url.searchParams.get('returnTo') ?? defaultReturnTo, | ||
}, | ||
}); | ||
const session = await getOrCreateSession(req, res); | ||
const sessionDetails = createSession(user.id); | ||
Object.assign(session, sessionDetails); | ||
await session.save(); | ||
return res; | ||
} | ||
@@ -242,13 +242,10 @@ | ||
} | ||
const res = new Response(null, { | ||
const session = await sessions.createSession(user.id); | ||
return new Response(null, { | ||
status: 302, | ||
headers: { | ||
...(await sessions.updateSession(session)), | ||
location: params.returnTo ?? defaultReturnTo, | ||
}, | ||
}); | ||
const session = await getOrCreateSession(req, res); | ||
const sessionDetails = await createSession(user.id); | ||
Object.assign(session, sessionDetails); | ||
await session.save(); | ||
return res; | ||
} | ||
@@ -303,13 +300,10 @@ | ||
} | ||
const res = new Response(null, { | ||
const session = await sessions.createSession(user.id); | ||
return new Response(null, { | ||
status: 302, | ||
headers: { | ||
...(await sessions.updateSession(session)), | ||
location: url.searchParams.get('returnTo') ?? defaultReturnTo, | ||
}, | ||
}); | ||
const session = await getOrCreateSession(req, res); | ||
const sessionDetails = await createSession(user.id); | ||
Object.assign(session, sessionDetails); | ||
await session.save(); | ||
return res; | ||
} | ||
@@ -316,0 +310,0 @@ |
@@ -1,10 +0,3 @@ | ||
import { getIronSession } from 'iron-session'; | ||
import { SignJWT, jwtVerify } from 'jose'; | ||
const SESSION_COOKIE = 'lg-session'; | ||
const SESSION_SECRET = process.env.SESSION_SECRET!; | ||
if (!SESSION_SECRET) { | ||
throw new Error('SESSION_SECRET must be set'); | ||
} | ||
export interface Session { | ||
@@ -14,18 +7,109 @@ userId: string; | ||
export async function getLiveSession( | ||
req: Request, | ||
res: Response, | ||
): Promise<Session | null> { | ||
const rawSession = await getOrCreateSession(req, res); | ||
if (!rawSession.userId) { | ||
return null; | ||
export type ShortNames = { | ||
[key in keyof Session]: string; | ||
}; | ||
export const defaultShortNames = { | ||
userId: 'sub', | ||
}; | ||
export class SessionManager { | ||
private secret; | ||
private shortNamesBackwards: Record<string, keyof Session>; | ||
constructor( | ||
private options: { | ||
secret: string; | ||
cookieName: string; | ||
shortNames: ShortNames; | ||
mode?: 'production' | 'development'; | ||
createSession: (userId: string) => Promise<Session>; | ||
issuer?: string; | ||
audience?: string; | ||
}, | ||
) { | ||
this.secret = new TextEncoder().encode(options.secret); | ||
this.shortNamesBackwards = Object.fromEntries( | ||
Object.entries(options.shortNames).map(([key, value]) => [value, key]), | ||
) as any; | ||
// validate shortnames don't repeat | ||
const values = Object.values(options.shortNames); | ||
if (new Set(values).size !== values.length) { | ||
throw new Error('Short names must be unique'); | ||
} | ||
} | ||
return rawSession as Session; | ||
} | ||
export function getOrCreateSession(req: Request, res: Response) { | ||
return getIronSession<Partial<Session>>(req, res, { | ||
cookieName: SESSION_COOKIE, | ||
password: SESSION_SECRET, | ||
}); | ||
createSession = async (userId: string): Promise<Session> => { | ||
return this.options.createSession(userId); | ||
}; | ||
getSession = async (req: Request) => { | ||
const cookie = req.headers.get('cookie') ?? ''; | ||
const match = cookie.match( | ||
new RegExp(`${this.options.cookieName}=([^;]+)`), | ||
); | ||
if (!match) { | ||
return null; | ||
} | ||
const cookieValue = match[1]; | ||
// read the JWT from the cookie | ||
const jwt = await jwtVerify(cookieValue, this.secret); | ||
// convert the JWT claims to a session object | ||
const session: Session = Object.fromEntries( | ||
Object.entries(jwt).map(([key, value]) => [this.getLongName(key), value]), | ||
) as any; | ||
// in dev mode, validate session has the right keys | ||
if (this.options.mode === 'development') { | ||
const keys = Object.keys(session); | ||
const expectedKeys = Object.keys(this.options.shortNames); | ||
if (keys.length !== expectedKeys.length) { | ||
throw new Error('Session has the wrong number of keys'); | ||
} | ||
for (const key of keys) { | ||
if (!expectedKeys.includes(key)) { | ||
throw new Error(`Session has unexpected key: ${key}`); | ||
} | ||
} | ||
} | ||
return session; | ||
}; | ||
updateSession = async (session: Session): Promise<HeadersInit> => { | ||
const builder = new SignJWT( | ||
Object.fromEntries( | ||
Object.entries(session).map(([key, value]) => [ | ||
this.getShortName(key), | ||
value, | ||
]), | ||
) as any, | ||
) | ||
.setProtectedHeader({ alg: 'HS256' }) | ||
.setIssuedAt() | ||
.setExpirationTime('1h'); | ||
if (this.options.issuer) { | ||
builder.setIssuer(this.options.issuer); | ||
} | ||
if (this.options.audience) { | ||
builder.setAudience(this.options.audience); | ||
} | ||
const jwt = builder.sign(this.secret); | ||
return { | ||
'Set-Cookie': `${this.options.cookieName}=${jwt}; Path=/; HttpOnly; SameSite=Strict`, | ||
}; | ||
}; | ||
clearSession = (): HeadersInit => { | ||
return { | ||
'Set-Cookie': `${this.options.cookieName}=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0`, | ||
}; | ||
}; | ||
private getShortName = (key: string) => { | ||
return (this.options.shortNames as any)[key]; | ||
}; | ||
private getLongName = (shortName: string) => { | ||
return this.shortNamesBackwards[shortName]; | ||
}; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
66212
12.1%1401
10.05%1
-50%+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed