Created
December 20, 2020 01:16
-
-
Save Jordan-Hall/628f703c8adfbfeb5994c0e4342b7914 to your computer and use it in GitHub Desktop.
Alosaur Session JWT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { AuthClaims, AuthenticationScheme, Identity } from "https://deno.land/x/[email protected]/src/security/authentication/core/mod.ts"; | |
import { Content } from "https://deno.land/x/[email protected]/mod.ts"; | |
import { SecurityContext } from "https://deno.land/x/[email protected]/src/security/context/security-context.ts"; | |
import { verify as verifySignature } from "https://deno.land/x/[email protected]/_signature.ts"; | |
import { create, decode, getNumericDate } from "https://deno.land/x/[email protected]/mod.ts"; | |
import { Algorithm } from "https://deno.land/x/[email protected]/_algorithm.ts"; | |
import { DAYS_30 } from "../../../auth/environmental.ts"; | |
export const IdentityKey = "__identity_jwt"; | |
const AuthorizationHeader = "Authorization"; | |
const AcceptHeader = "Accept"; | |
const AcceptTypeJSON = "application/json"; | |
export class LibertyJWTSchema implements AuthenticationScheme { | |
constructor( | |
protected readonly algorithm: Algorithm, | |
protected readonly secret: string, | |
protected readonly expires: number = DAYS_30, | |
) { | |
} | |
async authenticate(context: SecurityContext): Promise<void> { | |
const token = this.getToken(context); | |
if (token) { | |
const payload = await safeVerifyJWT(token, this.secret); | |
if (payload) { | |
const session = context.security.session; | |
const currentSessions = await session?.get<{ [key: string]: string }>(IdentityKey); | |
if (currentSessions && currentSessions[payload.id]?.includes(token)) { | |
context.security.auth.identity = <T>() => payload as Identity<T>; | |
} | |
} | |
} | |
return undefined; | |
} | |
public async signInAsync<I, R>( | |
context: SecurityContext, | |
identity: Identity<I>, | |
claims?: AuthClaims, | |
): Promise<R> { | |
const jwt = await create( | |
{ alg: this.algorithm, typ: "JWT" }, | |
{ exp: getNumericDate(this.expires), ...identity }, | |
this.secret, | |
); | |
context.security.auth.identity = () => identity as any; | |
const session = context.security.session; | |
const currentSessions = await session?.get<{ [key: string]: string }>(IdentityKey); | |
if (jwt) { | |
await session?.set(IdentityKey, { | |
...currentSessions, | |
[identity.id]: [ | |
...((currentSessions || {})[identity.id] || []), | |
jwt, | |
], | |
}); | |
} | |
return { access_token: jwt } as unknown as Promise<R>; | |
} | |
async signOutAsync<T, R>(context: SecurityContext): Promise<R> { | |
const session = context.security.session; | |
const identity: Identity<unknown> = context.security.auth.identity() as Identity<unknown>; | |
const loggedInToken = this.getToken(context); | |
const currentSessions = await session?.get<{ [key: string]: string[] }>(IdentityKey); | |
if (currentSessions) { | |
await session?.set(IdentityKey, { | |
...currentSessions, | |
[identity.id]: currentSessions[identity.id]?.filter(token => token !== loggedInToken) | |
}); | |
} | |
return <unknown> true as R; | |
} | |
onFailureResult(context: SecurityContext): void { | |
context.response.result = Content({ status: 401 }, 401); | |
context.response.setImmediately(); | |
} | |
onSuccessResult(context: SecurityContext): void { | |
// nothing | |
} | |
private getToken(context: SecurityContext) { | |
const headers = context.request.serverRequest.headers; | |
const headAuthorization = headers.get(AuthorizationHeader); | |
const headAccept = headers.get(AcceptHeader); | |
if (headAccept === AcceptTypeJSON && headAuthorization) { | |
return getBearerToken(headAuthorization); | |
} | |
} | |
} | |
function getBearerToken(authHeader: string): string | undefined { | |
const head = authHeader.substr(0, 7); | |
const token = authHeader.slice(7); | |
if (head === "Bearer ") { | |
return token; | |
} | |
return undefined; | |
} | |
async function safeVerifyJWT( | |
jwt: string, | |
key: string, | |
): Promise<Identity<unknown> | undefined> { | |
const { header, payload, signature } = decode(jwt); | |
if ( | |
!(await verifySignature({ | |
signature, | |
key, | |
algorithm: header.alg, | |
signingInput: jwt.slice(0, jwt.lastIndexOf(".")), | |
})) | |
) { | |
return undefined; | |
} | |
return payload as unknown as Identity<unknown>; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment