-
-
Notifications
You must be signed in to change notification settings - Fork 47
/
session.middleware.ts
135 lines (115 loc) · 3.76 KB
/
session.middleware.ts
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import * as secp from "https://deno.land/x/secp256k1@1.2.9/mod.ts";
import { MiddlewareTarget } from "../../../models/middleware-target.ts";
import { HttpContext } from "../../../models/http-context.ts";
import { SessionStore } from "./store/store.interface.ts";
import { Session } from "./session.instance.ts";
import { getCookies, setCookie } from "https://deno.land/std@0.122.0/http/cookie.ts";
import { SESSION_SIGNATURE_PREFIX_KEY, SessionOptions } from "./session.interface.ts";
import { SecurityContext } from "../../context/security-context.ts";
const DEFAULT_SESSION_COOKIE_KEY = "sid";
const DEFAULT_MAX_AGE = 24 * 60 * 60 * 1000; // day
const EXPIRES_STORE_KEY = "__alosaur-expires";
/**
* Middleware for use sessions with signature hash
* DEFAULTS:
* DEFAULT_SESSION_COOKIE_KEY: sid
* DEFAULT_MAX_AGE: day
*/
export class SessionMiddleware implements MiddlewareTarget {
private readonly cookieKey: string;
private readonly publicKey: any;
/**
* Creates instance
* @param store
* @param options
*/
constructor(
private readonly store: SessionStore,
private readonly options: SessionOptions,
) {
this.cookieKey = options.name || DEFAULT_SESSION_COOKIE_KEY;
this.publicKey = secp.getPublicKey(options.secret as any);
}
/**
* wrapped context on request
* @param context
*/
async onPreRequest(context: SecurityContext) {
let session: Session;
const sessionId = this.getSessionIdCookie(context);
if (sessionId === undefined || !await this.store.exist(sessionId)) {
session = await this.createNewSession(context);
} else {
session = new Session(this.store, this.cookieKey, sessionId);
if (await this.isSessionExpired(session)) {
await this.store.delete(sessionId);
session = await this.createNewSession(context);
}
}
this.assignToContext(context, session);
}
onPostRequest() {
// do nothing
}
/**
* Create new session with expires
* @param context
* @private
*/
private async createNewSession(context: SecurityContext): Promise<Session> {
const session = new Session(this.store, this.cookieKey);
await this.store.create(session.sessionId);
await session.set(
EXPIRES_STORE_KEY,
Date.now() + (this.options.maxAge || DEFAULT_MAX_AGE),
);
await this.setSessionIdCookie(session.sessionId, context);
return session;
}
private async isSessionExpired(session: Session) {
const expires: number = Number(await session.get(EXPIRES_STORE_KEY));
return expires <= Date.now();
}
private getSessionIdCookie(context: SecurityContext): string | undefined {
const cookies = getCookies(context.request.serverRequest.request.headers);
const sidHash = cookies[this.cookieKey];
const sign = cookies[this.cookieKey + SESSION_SIGNATURE_PREFIX_KEY];
if (this.isValidSessionId(sidHash, sign)) {
return sidHash;
}
return undefined;
}
private async setSessionIdCookie(
sessionIdHash: string,
context: HttpContext,
): Promise<void> {
const sign = await secp.sign(sessionIdHash, this.options.secret);
// set hash
setCookie(
context.response.headers,
{
path: "/",
...this.options,
name: this.cookieKey,
value: sessionIdHash,
},
);
// set signature
setCookie(
context.response.headers,
{
path: "/",
...this.options,
name: this.cookieKey + SESSION_SIGNATURE_PREFIX_KEY,
value: sign.toString(),
},
);
}
private isValidSessionId(sidHash: string, sign: string): boolean {
if (!sidHash) return false;
return secp.verify(sign, sidHash, this.publicKey);
}
private assignToContext(context: SecurityContext, session: Session) {
context.security.session = session;
}
}