-
Notifications
You must be signed in to change notification settings - Fork 52
/
token.ts
144 lines (124 loc) · 3.8 KB
/
token.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
136
137
138
139
140
141
142
143
144
import jwt, { SignOptions } from 'jsonwebtoken';
import { Context, Middleware } from 'koa';
import { getRepository } from 'typeorm';
import User from '../entity/User';
import loadVariables from '../loadVariable';
const { SECRET_KEY } = process.env;
if (!SECRET_KEY && process.env.NODE_ENV === 'development') {
const error = new Error('InvalidSecretKeyError');
error.message = 'Secret key for JWT is missing.';
if (process.env.npm_lifecycle_event !== 'typeorm') throw error;
}
export const generateToken = async (payload: any, options?: SignOptions): Promise<string> => {
const jwtOptions: SignOptions = {
issuer: 'velog.io',
expiresIn: '7d',
...options,
};
if (!jwtOptions.expiresIn) {
// removes expiresIn when expiresIn is given as undefined
delete jwtOptions.expiresIn;
}
return new Promise((resolve, reject) => {
if (!SECRET_KEY) return;
jwt.sign(payload, SECRET_KEY, jwtOptions, (err, token) => {
if (err) reject(err);
resolve(token);
});
});
};
export const decodeToken = async <T = any>(token: string): Promise<T> => {
return new Promise((resolve, reject) => {
if (!SECRET_KEY) return;
jwt.verify(token, SECRET_KEY, (err, decoded) => {
if (err) reject(err);
resolve(decoded as any);
});
});
};
const domains = ['.velog.io', undefined];
export function setTokenCookie(ctx: any, tokens: { accessToken: string; refreshToken: string }) {
domains.forEach(domain => {
// set cookie
ctx.cookies.set('access_token', tokens.accessToken, {
httpOnly: true,
maxAge: 1000 * 60 * 60,
domain: domain,
});
ctx.cookies.set('refresh_token', tokens.refreshToken, {
httpOnly: true,
maxAge: 1000 * 60 * 60 * 24 * 30,
domain: domain,
});
});
}
export function resetTokenCookie(ctx: Context) {
domains.forEach(domain => {
ctx.cookies.set('access_token', '', {
maxAge: 0,
domain,
});
ctx.cookies.set('refresh_token', '', {
maxAge: 0,
domain,
});
});
}
type TokenData = {
iat: number;
exp: number;
sub: string;
iss: string;
};
type AccessTokenData = {
user_id: string;
} & TokenData;
type RefreshTokenData = {
user_id: string;
token_id: string;
} & TokenData;
export const refresh = async (ctx: Context, refreshToken: string) => {
try {
const decoded = await decodeToken<RefreshTokenData>(refreshToken);
const user = await getRepository(User).findOne(decoded.user_id);
if (!user) {
const error = new Error('InvalidUserError');
throw error;
}
const tokens = await user.refreshUserToken(decoded.token_id, decoded.exp, refreshToken);
setTokenCookie(ctx, tokens);
return decoded.user_id;
} catch (e) {
throw e;
}
};
export const consumeUser: Middleware = async (ctx: Context, next) => {
if (ctx.path.includes('/auth/logout')) return next(); // ignore when logging out
let accessToken: string | undefined = ctx.cookies.get('access_token');
const refreshToken: string | undefined = ctx.cookies.get('refresh_token');
const { authorization } = ctx.request.headers;
if (!accessToken && authorization) {
accessToken = authorization.split(' ')[1];
}
try {
if (!accessToken) {
throw new Error('NoAccessToken');
}
const accessTokenData = await decodeToken<AccessTokenData>(accessToken);
ctx.state.user_id = accessTokenData.user_id;
// refresh token when life < 30mins
const diff = accessTokenData.exp * 1000 - new Date().getTime();
if (diff < 1000 * 60 * 30 && refreshToken) {
await refresh(ctx, refreshToken);
}
} catch (e) {
// invalid token! try token refresh...
if (!refreshToken) return next();
try {
const userId = await refresh(ctx, refreshToken);
// set user_id if succeeds
ctx.state.user_id = userId;
} catch (e) {}
}
return next();
};