@a-type/auth
Advanced tools
Comparing version 0.4.9 to 0.4.11
@@ -353,10 +353,22 @@ import { AuthError } from './error.js'; | ||
} | ||
const { headers, searchParams } = await sessions.refreshSession(accessToken, refreshToken); | ||
return new Response(JSON.stringify({ | ||
ok: true, | ||
refreshToken: searchParams.get('refreshToken'), | ||
}), { | ||
status: 200, | ||
headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json' }), | ||
}); | ||
try { | ||
const { headers, searchParams } = await sessions.refreshSession(accessToken, refreshToken); | ||
return new Response(JSON.stringify({ | ||
ok: true, | ||
refreshToken: searchParams.get('refreshToken'), | ||
}), { | ||
status: 200, | ||
headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json' }), | ||
}); | ||
} | ||
catch (err) { | ||
if (err instanceof AuthError && err.statusCode === 401) { | ||
return new Response(JSON.stringify({ | ||
ok: false, | ||
}), { | ||
status: 401, | ||
headers: Object.assign(Object.assign({}, sessions.clearSession().headers), { 'content-type': 'application/json' }), | ||
}); | ||
} | ||
} | ||
} | ||
@@ -363,0 +375,0 @@ return { |
@@ -69,14 +69,23 @@ import { parse, serialize } from 'cookie'; | ||
this.refreshSession = async (accessToken, refreshToken) => { | ||
const refreshData = await jwtVerify(refreshToken, this.secret, { | ||
issuer: this.options.issuer, | ||
audience: this.options.audience, | ||
}); | ||
// verify the signature of the token | ||
await compactVerify(accessToken, this.secret); | ||
const accessData = decodeJwt(accessToken); | ||
if (refreshData.payload.jti !== accessData.jti) { | ||
try { | ||
const refreshData = await jwtVerify(refreshToken, this.secret, { | ||
issuer: this.options.issuer, | ||
audience: this.options.audience, | ||
}); | ||
// verify the signature of the token | ||
await compactVerify(accessToken, this.secret); | ||
const accessData = decodeJwt(accessToken); | ||
if (refreshData.payload.jti !== accessData.jti) { | ||
throw new AuthError('Invalid refresh token', 400); | ||
} | ||
const session = this.readSessionFromPayload(accessData); | ||
return this.updateSession(session, { sendRefreshToken: true }); | ||
} | ||
catch (err) { | ||
if (err instanceof Error && | ||
(err.message.includes('JWTExpired') || err.name === 'JWTExpired')) { | ||
throw new AuthError('Refresh token expired', 401); | ||
} | ||
throw new AuthError('Invalid refresh token', 400); | ||
} | ||
const session = this.readSessionFromPayload(accessData); | ||
return this.updateSession(session, { sendRefreshToken: true }); | ||
}; | ||
@@ -87,2 +96,3 @@ this.updateSession = async (session, { sendRefreshToken, } = { sendRefreshToken: false }) => { | ||
const jwt = await accessTokenBuilder.sign(this.secret); | ||
const parsed = decodeJwt(jwt); | ||
const authCookie = serialize(this.options.cookieName, jwt, { | ||
@@ -92,2 +102,13 @@ httpOnly: true, | ||
path: '/', | ||
secure: this.options.mode === 'production', | ||
// sync access token expiration to refresh token - an expired token | ||
// will still be presented to the server, but the server will reject it | ||
// as expired. the api can then tell the client the token is expired | ||
// and the refresh should be used. once the access token cookie is expired | ||
// and removed, it will instead trigger a fully logged out state. | ||
expires: sendRefreshToken | ||
? this.getRefreshTokenExpirationTime() | ||
: parsed.exp | ||
? new Date(parsed.exp * 1000) | ||
: undefined, | ||
}); | ||
@@ -102,2 +123,3 @@ const headers = { | ||
searchParams.set(this.refreshParam, refreshToken); | ||
searchParams.set('refreshTokenExpires', this.getRefreshTokenExpirationTime().toISOString()); | ||
} | ||
@@ -110,2 +132,4 @@ return { | ||
this.clearSession = () => { | ||
const searchParams = new URLSearchParams(); | ||
searchParams.set(this.refreshParam, 'clear'); | ||
return { | ||
@@ -115,2 +139,3 @@ headers: { | ||
}, | ||
searchParams, | ||
}; | ||
@@ -143,3 +168,3 @@ }; | ||
.setIssuedAt() | ||
.setExpirationTime('7d'); | ||
.setExpirationTime(this.getRefreshTokenExpirationTime()); | ||
if (this.options.issuer) { | ||
@@ -153,2 +178,7 @@ refreshTokenBuilder.setIssuer(this.options.issuer); | ||
}; | ||
this.getRefreshTokenExpirationTime = () => { | ||
var _a; | ||
const msFromNow = ((_a = this.options.refreshTokenDurationMinutes) !== null && _a !== void 0 ? _a : 60 * 24 * 14) * 60 * 1000; | ||
return new Date(Date.now() + msFromNow); | ||
}; | ||
this.getShortName = (key) => { | ||
@@ -155,0 +185,0 @@ return this.options.shortNames[key]; |
@@ -86,3 +86,3 @@ import { it, describe, vi, beforeAll, expect } from 'vitest'; | ||
// verify that the refresh token is rejected | ||
expect(sessions.refreshSession(badToken, refreshToken)).rejects.toThrowError('signature verification failed'); | ||
expect(sessions.refreshSession(badToken, refreshToken)).rejects.toThrowError('Invalid refresh token'); | ||
// even a token signed with the right signature whose JTI doesn't | ||
@@ -89,0 +89,0 @@ // match is rejected |
@@ -43,3 +43,3 @@ import { AuthDB } from './db.js'; | ||
handleSessionRequest: (req: Request) => Promise<Response>; | ||
handleRefreshSessionRequest: (req: Request) => Promise<Response>; | ||
handleRefreshSessionRequest: (req: Request) => Promise<Response | undefined>; | ||
}; |
@@ -18,2 +18,3 @@ export interface Session { | ||
refreshParam?: string; | ||
refreshTokenDurationMinutes?: number; | ||
shortNames: ShortNames; | ||
@@ -56,5 +57,7 @@ mode?: 'production' | 'development'; | ||
}; | ||
searchParams: URLSearchParams; | ||
}; | ||
private getAccessTokenBuilder; | ||
private getRefreshTokenBuilder; | ||
private getRefreshTokenExpirationTime; | ||
private getShortName; | ||
@@ -61,0 +64,0 @@ private getLongName; |
{ | ||
"name": "@a-type/auth", | ||
"version": "0.4.9", | ||
"version": "0.4.11", | ||
"description": "My personal auth request handlers", | ||
@@ -5,0 +5,0 @@ "module": "dist/esm/index.js", |
@@ -456,20 +456,37 @@ import { AuthDB } from './db.js'; | ||
const { headers, searchParams } = await sessions.refreshSession( | ||
accessToken, | ||
refreshToken, | ||
); | ||
try { | ||
const { headers, searchParams } = await sessions.refreshSession( | ||
accessToken, | ||
refreshToken, | ||
); | ||
return new Response( | ||
JSON.stringify({ | ||
ok: true, | ||
refreshToken: searchParams.get('refreshToken'), | ||
}), | ||
{ | ||
status: 200, | ||
headers: { | ||
...headers, | ||
'content-type': 'application/json', | ||
return new Response( | ||
JSON.stringify({ | ||
ok: true, | ||
refreshToken: searchParams.get('refreshToken'), | ||
}), | ||
{ | ||
status: 200, | ||
headers: { | ||
...headers, | ||
'content-type': 'application/json', | ||
}, | ||
}, | ||
}, | ||
); | ||
); | ||
} catch (err) { | ||
if (err instanceof AuthError && err.statusCode === 401) { | ||
return new Response( | ||
JSON.stringify({ | ||
ok: false, | ||
}), | ||
{ | ||
status: 401, | ||
headers: { | ||
...sessions.clearSession().headers, | ||
'content-type': 'application/json', | ||
}, | ||
}, | ||
); | ||
} | ||
} | ||
} | ||
@@ -476,0 +493,0 @@ |
@@ -117,3 +117,3 @@ import { it, describe, vi, beforeAll, expect } from 'vitest'; | ||
sessions.refreshSession(badToken, refreshToken), | ||
).rejects.toThrowError('signature verification failed'); | ||
).rejects.toThrowError('Invalid refresh token'); | ||
@@ -120,0 +120,0 @@ // even a token signed with the right signature whose JTI doesn't |
@@ -34,2 +34,3 @@ import { parse, serialize } from 'cookie'; | ||
refreshParam?: string; | ||
refreshTokenDurationMinutes?: number; | ||
shortNames: ShortNames; | ||
@@ -117,19 +118,29 @@ mode?: 'production' | 'development'; | ||
refreshSession = async (accessToken: string, refreshToken: string) => { | ||
const refreshData = await jwtVerify(refreshToken, this.secret, { | ||
issuer: this.options.issuer, | ||
audience: this.options.audience, | ||
}); | ||
try { | ||
const refreshData = await jwtVerify(refreshToken, this.secret, { | ||
issuer: this.options.issuer, | ||
audience: this.options.audience, | ||
}); | ||
// verify the signature of the token | ||
await compactVerify(accessToken, this.secret); | ||
// verify the signature of the token | ||
await compactVerify(accessToken, this.secret); | ||
const accessData = decodeJwt(accessToken); | ||
const accessData = decodeJwt(accessToken); | ||
if (refreshData.payload.jti !== accessData.jti) { | ||
if (refreshData.payload.jti !== accessData.jti) { | ||
throw new AuthError('Invalid refresh token', 400); | ||
} | ||
const session = this.readSessionFromPayload(accessData); | ||
return this.updateSession(session, { sendRefreshToken: true }); | ||
} catch (err) { | ||
if ( | ||
err instanceof Error && | ||
(err.message.includes('JWTExpired') || err.name === 'JWTExpired') | ||
) { | ||
throw new AuthError('Refresh token expired', 401); | ||
} | ||
throw new AuthError('Invalid refresh token', 400); | ||
} | ||
const session = this.readSessionFromPayload(accessData); | ||
return this.updateSession(session, { sendRefreshToken: true }); | ||
}; | ||
@@ -148,2 +159,3 @@ | ||
const jwt = await accessTokenBuilder.sign(this.secret); | ||
const parsed = decodeJwt(jwt); | ||
@@ -154,2 +166,13 @@ const authCookie = serialize(this.options.cookieName, jwt, { | ||
path: '/', | ||
secure: this.options.mode === 'production', | ||
// sync access token expiration to refresh token - an expired token | ||
// will still be presented to the server, but the server will reject it | ||
// as expired. the api can then tell the client the token is expired | ||
// and the refresh should be used. once the access token cookie is expired | ||
// and removed, it will instead trigger a fully logged out state. | ||
expires: sendRefreshToken | ||
? this.getRefreshTokenExpirationTime() | ||
: parsed.exp | ||
? new Date(parsed.exp * 1000) | ||
: undefined, | ||
}); | ||
@@ -165,2 +188,6 @@ const headers: Record<string, string> = { | ||
searchParams.set(this.refreshParam, refreshToken); | ||
searchParams.set( | ||
'refreshTokenExpires', | ||
this.getRefreshTokenExpirationTime().toISOString(), | ||
); | ||
} | ||
@@ -175,2 +202,4 @@ | ||
clearSession = () => { | ||
const searchParams = new URLSearchParams(); | ||
searchParams.set(this.refreshParam, 'clear'); | ||
return { | ||
@@ -180,2 +209,3 @@ headers: { | ||
}, | ||
searchParams, | ||
}; | ||
@@ -214,3 +244,3 @@ }; | ||
.setIssuedAt() | ||
.setExpirationTime('7d'); | ||
.setExpirationTime(this.getRefreshTokenExpirationTime()); | ||
@@ -227,2 +257,8 @@ if (this.options.issuer) { | ||
private getRefreshTokenExpirationTime = () => { | ||
const msFromNow = | ||
(this.options.refreshTokenDurationMinutes ?? 60 * 24 * 14) * 60 * 1000; | ||
return new Date(Date.now() + msFromNow); | ||
}; | ||
private getShortName = (key: string) => { | ||
@@ -229,0 +265,0 @@ return (this.options.shortNames as any)[key]; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
122214
2457