-
Notifications
You must be signed in to change notification settings - Fork 454
Expand file tree
/
Copy pathtransaction-store.ts
More file actions
109 lines (94 loc) · 3.16 KB
/
transaction-store.ts
File metadata and controls
109 lines (94 loc) · 3.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import type * as jose from "jose";
import * as cookies from "./cookies";
const TRANSACTION_COOKIE_PREFIX = "__txn_";
export interface TransactionState extends jose.JWTPayload {
nonce: string;
codeVerifier: string;
responseType: string;
state: string; // the state parameter passed to the authorization server
returnTo: string; // the URL to redirect to after login
maxAge?: number; // the maximum age of the authentication session
}
export interface TransactionCookieOptions {
/**
* The prefix of the cookie used to store the transaction state.
*
* Default: `__txn_{state}`.
*/
prefix?: string;
/**
* The sameSite attribute of the transaction cookie.
*
* Default: `lax`.
*/
sameSite?: "strict" | "lax" | "none";
/**
* The secure attribute of the transaction cookie.
*
* Default: depends on the protocol of the application's base URL. If the protocol is `https`, then `true`, otherwise `false`.
*/
secure?: boolean;
/**
* The path attribute of the transaction cookie. Will be set to '/' by default.
*/
path?: string;
}
export interface TransactionStoreOptions {
secret: string;
cookieOptions?: TransactionCookieOptions;
}
/**
* TransactionStore is responsible for storing the state required to successfully complete
* an authentication transaction. The store relies on encrypted, stateless cookies to store
* the transaction state.
*/
export class TransactionStore {
private secret: string;
private transactionCookiePrefix: string;
private cookieConfig: cookies.CookieOptions;
constructor({ secret, cookieOptions }: TransactionStoreOptions) {
this.secret = secret;
this.transactionCookiePrefix =
cookieOptions?.prefix ?? TRANSACTION_COOKIE_PREFIX;
this.cookieConfig = {
httpOnly: true,
sameSite: cookieOptions?.sameSite ?? "lax", // required to allow the cookie to be sent on the callback request
secure: cookieOptions?.secure ?? false,
path: cookieOptions?.path ?? "/",
maxAge: 60 * 60 // 1 hour in seconds
};
}
/**
* Returns the name of the cookie used to store the transaction state.
* The cookie name is derived from the state parameter to prevent collisions
* between different transactions.
*/
private getTransactionCookieName(state: string) {
return `${this.transactionCookiePrefix}${state}`;
}
async save(
resCookies: cookies.ResponseCookies,
transactionState: TransactionState
) {
const jwe = await cookies.encrypt(transactionState, this.secret);
if (!transactionState.state) {
throw new Error("Transaction state is required");
}
resCookies.set(
this.getTransactionCookieName(transactionState.state),
jwe.toString(),
this.cookieConfig
);
}
async get(reqCookies: cookies.RequestCookies, state: string) {
const cookieName = this.getTransactionCookieName(state);
const cookieValue = reqCookies.get(cookieName)?.value;
if (!cookieValue) {
return null;
}
return cookies.decrypt<TransactionState>(cookieValue, this.secret);
}
async delete(resCookies: cookies.ResponseCookies, state: string) {
await resCookies.delete(this.getTransactionCookieName(state));
}
}