forked from outline/outline
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathauthentication.js
148 lines (130 loc) · 4.46 KB
/
authentication.js
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
145
146
147
148
// @flow
import JWT from 'jsonwebtoken';
import { type Context } from 'koa';
import { User, ApiKey } from '../models';
import { getUserForJWT } from '../utils/jwt';
import { AuthenticationError, UserSuspendedError } from '../errors';
import addMonths from 'date-fns/add_months';
import addMinutes from 'date-fns/add_minutes';
import { stripSubdomain } from '../../shared/utils/domains';
export default function auth(options?: { required?: boolean } = {}) {
return async function authMiddleware(ctx: Context, next: () => Promise<*>) {
let token;
const authorizationHeader = ctx.request.get('authorization');
if (authorizationHeader) {
const parts = authorizationHeader.split(' ');
if (parts.length === 2) {
const scheme = parts[0];
const credentials = parts[1];
if (/^Bearer$/i.test(scheme)) {
token = credentials;
}
} else {
throw new AuthenticationError(
`Bad Authorization header format. Format is "Authorization: Bearer <token>"`
);
}
// $FlowFixMe
} else if (ctx.body && ctx.body.token) {
token = ctx.body.token;
} else if (ctx.request.query.token) {
token = ctx.request.query.token;
} else {
token = ctx.cookies.get('accessToken');
}
if (!token && options.required !== false) {
throw new AuthenticationError('Authentication required');
}
let user;
if (token) {
if (String(token).match(/^[\w]{38}$/)) {
// API key
let apiKey;
try {
apiKey = await ApiKey.findOne({
where: {
secret: token,
},
});
} catch (e) {
throw new AuthenticationError('Invalid API key');
}
if (!apiKey) throw new AuthenticationError('Invalid API key');
user = await User.findByPk(apiKey.userId);
if (!user) throw new AuthenticationError('Invalid API key');
} else {
// JWT
user = await getUserForJWT(token);
}
if (user.isSuspended) {
const suspendingAdmin = await User.findOne({
where: { id: user.suspendedById },
paranoid: false,
});
throw new UserSuspendedError({ adminEmail: suspendingAdmin.email });
}
// not awaiting the promise here so that the request is not blocked
user.updateActiveAt(ctx.request.ip);
ctx.state.token = token;
ctx.state.user = user;
if (!ctx.cache) ctx.cache = {};
ctx.cache[user.id] = user;
}
ctx.signIn = async (user, team, service, isFirstSignin = false) => {
if (user.isSuspended) {
return ctx.redirect('/?notice=suspended');
}
// update the database when the user last signed in
user.updateSignedIn(ctx.request.ip);
const domain = stripSubdomain(ctx.request.hostname);
const expires = addMonths(new Date(), 3);
// set a cookie for which service we last signed in with. This is
// only used to display a UI hint for the user for next time
ctx.cookies.set('lastSignedIn', service, {
httpOnly: false,
expires: new Date('2100'),
domain,
});
// set a transfer cookie for the access token itself and redirect
// to the teams subdomain if subdomains are enabled
if (process.env.SUBDOMAINS_ENABLED === 'true' && team.subdomain) {
// get any existing sessions (teams signed in) and add this team
const existing = JSON.parse(
decodeURIComponent(ctx.cookies.get('sessions') || '') || '{}'
);
const sessions = encodeURIComponent(
JSON.stringify({
...existing,
[team.id]: {
name: team.name,
logoUrl: team.logoUrl,
url: team.url,
},
})
);
ctx.cookies.set('sessions', sessions, {
httpOnly: false,
expires,
domain,
});
ctx.cookies.set('accessToken', user.getJwtToken(), {
httpOnly: true,
expires: addMinutes(new Date(), 1),
domain,
});
ctx.redirect(`${team.url}/auth/redirect`);
} else {
ctx.cookies.set('accessToken', user.getJwtToken(), {
httpOnly: false,
expires,
});
ctx.redirect(`${team.url}/dashboard${isFirstSignin ? '?welcome' : ''}`);
}
};
return next();
};
}
// Export JWT methods as a convenience
export const sign = JWT.sign;
export const verify = JWT.verify;
export const decode = JWT.decode;