forked from canada-ca/tracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauthenticate.js
More file actions
180 lines (162 loc) · 5.21 KB
/
authenticate.js
File metadata and controls
180 lines (162 loc) · 5.21 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
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import { GraphQLNonNull, GraphQLString, GraphQLInt } from 'graphql'
import { mutationWithClientMutationId } from 'graphql-relay'
import { t } from '@lingui/macro'
import { authenticateUnion } from '../unions'
const { SIGN_IN_KEY, REFRESH_KEY, REFRESH_TOKEN_EXPIRY } = process.env
export const authenticate = new mutationWithClientMutationId({
name: 'Authenticate',
description:
'This mutation allows users to give their credentials and retrieve a token that gives them access to restricted content.',
inputFields: () => ({
authenticationCode: {
type: GraphQLNonNull(GraphQLInt),
description: 'Security code found in text msg, or email inbox.',
},
authenticateToken: {
type: GraphQLNonNull(GraphQLString),
description: 'The JWT that is retrieved from the sign in mutation.',
},
}),
outputFields: () => ({
result: {
type: authenticateUnion,
description:
'Authenticate union returning either a `authResult` or `authenticateError` object.',
resolve: (payload) => payload,
},
}),
mutateAndGetPayload: async (
args,
{
i18n,
response,
query,
collections,
transaction,
uuidv4,
auth: { tokenize, verifyToken },
loaders: { loadUserByKey },
validators: { cleanseInput },
},
) => {
// Cleanse Inputs
const authenticationCode = args.authenticationCode
const authenticationToken = cleanseInput(args.authenticateToken)
// Gather token parameters
const tokenParameters = verifyToken({
token: authenticationToken,
secret: String(SIGN_IN_KEY),
})
if (
tokenParameters.userKey === 'undefined' ||
typeof tokenParameters.userKey === 'undefined'
) {
console.warn(`Authentication token does not contain the userKey`)
return {
_type: 'error',
code: 400,
description: i18n._(t`Token value incorrect, please sign in again.`),
}
}
// Gather sign in user
const user = await loadUserByKey.load(tokenParameters.userKey)
// Replace with userRequired()
if (typeof user === 'undefined') {
console.warn(
`User: ${tokenParameters.userKey} attempted to authenticate, no account is associated with this id.`,
)
return {
_type: 'error',
code: 400,
description: i18n._(t`Unable to authenticate. Please try again.`),
}
}
// Check to see if security token matches the user submitted one
if (authenticationCode === user.tfaCode) {
const refreshId = uuidv4()
const refreshInfo = {
refreshInfo: {
refreshId,
rememberMe: user.refreshInfo.rememberMe,
expiresAt: new Date(
new Date().getTime() + REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000,
),
},
}
// Generate list of collections names
const collectionStrings = []
for (const property in collections) {
collectionStrings.push(property.toString())
}
// Setup Transaction
const trx = await transaction(collectionStrings)
// Reset tfa code attempts, and set refresh code
try {
await trx.step(
() => query`
WITH users
UPSERT { _key: ${user._key} }
INSERT {
tfaCode: null,
refreshInfo: ${refreshInfo}
}
UPDATE {
tfaCode: null,
refreshInfo: ${refreshInfo}
}
IN users
`,
)
} catch (err) {
console.error(
`Trx step error occurred when clearing tfa code and setting refresh id for user: ${user._key} during authentication: ${err}`,
)
throw new Error(i18n._(t`Unable to authenticate. Please try again.`))
}
try {
await trx.commit()
} catch (err) {
console.error(
`Trx commit error occurred while user: ${user._key} attempted to authenticate: ${err}`,
)
throw new Error(i18n._(t`Unable to authenticate. Please try again.`))
}
const token = tokenize({ parameters: { userKey: user._key } })
const refreshToken = tokenize({
parameters: { userKey: user._key, uuid: refreshId },
expPeriod: 168,
secret: String(REFRESH_KEY),
})
// if the user does not want to stay logged in, create http session cookie
let cookieData = {
httpOnly: true,
secure: true,
sameSite: true,
expires: 0,
}
// if user wants to stay logged in create normal http cookie
if (user.refreshInfo.rememberMe) {
cookieData = {
maxAge: REFRESH_TOKEN_EXPIRY * 60 * 24 * 60 * 1000,
httpOnly: true,
secure: true,
sameSite: true,
}
}
response.cookie('refresh_token', refreshToken, cookieData)
console.info(
`User: ${user._key} successfully authenticated their account.`,
)
return {
_type: 'authResult',
token,
user,
}
} else {
console.warn(
`User: ${user._key} attempted to authenticate their account, however the tfaCodes did not match.`,
)
throw new Error(i18n._(t`Incorrect TFA code. Please sign in again.`))
}
},
})