-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
174 lines (159 loc) · 3.55 KB
/
index.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
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
import envSchema from 'env-schema'
import fp from 'fastify-plugin'
import session from '@fastify/secure-session'
import securePassword from 'secure-password'
const pwd = securePassword()
const schema = {
type: 'object',
required: ['key'],
properties: {
key: {},
decorateRequest: {
default: 'user'
},
collection: {
default: 'accounts'
},
filter: {
default: {}
},
usernameToLowerCase: {
default: true
},
usernameField: {
default: 'username'
},
passwordField: {
default: 'password'
}
}
}
const fastifyMongoAuth = async (fastify, opts, next) => {
const {
decorateRequest: user,
collection,
filter,
usernameToLowerCase,
usernameField,
passwordField
} = envSchema({
schema,
data: opts,
dotenv: opts.useDotenv
})
fastify.decorateRequest(user, null)
/**
* session config
*/
if (!fastify.hasRequestDecorator('session')) {
fastify.register(session, {
key: opts.key,
cookie: Object.assign(
{
path: '/'
},
opts.cookie
)
})
}
/**
* verify any request with existing session
*/
fastify.addHook('preHandler', async (req, res) => {
req[user] = null
const sid = req.session.get('_id')
try {
req[user] =
sid &&
(await auth.collection.findOne({
_id: fastify.mongo.ObjectId(sid),
...filter
}))
} catch (err) /* c8 ignore start */ {
fastify.log.error(err)
}
/* c8 ignore stop */
})
/**
* auth object factory
*/
const auth = {
get collection() {
return fastify.crud(collection)
},
/**
* utility to require auth on some routes
*/
authorized(req, res, next) {
const _id = req.session.get('_id')
if (_id && req[user] && _id === req[user]._id.toString()) {
return next()
}
return res.unauthorized()
},
/**
* hashing
*/
createHash(password) {
return pwd.hashSync(Buffer.from(password)).toString('base64')
},
/**
* verification
*/
verifyHash(password, hash) {
const verified = pwd.verifySync(
Buffer.from(password),
Buffer.from(hash, 'base64')
)
return verified === securePassword.VALID
},
/**
* handler to be called on POST /login
*/
async loginHandler(req) {
const query = {}
query[usernameField] = usernameToLowerCase
? req.body[usernameField].toLowerCase()
: req.body[usernameField]
const account = await auth.collection
.findOne({ ...query, ...filter })
.catch((e) => {
/* istanbul ignore next */
fastify.log.error(e)
})
if (account && auth.verifyHash(req.body[passwordField], account.hash)) {
req.session.set('_id', account._id)
return { account }
}
throw fastify.httpErrors.unauthorized()
},
/**
* handler to be called on GET /logout
*/
async logoutHandler(req) {
req.session.delete()
return {}
},
/**
* handler to be called on GET /currentUser
*/
async currentUserHandler(req) {
const currentUser = {}
currentUser[user] = req[user]
return currentUser
}
}
/**
* decorate fastify app with auth object
*/
fastify.decorate('auth', auth)
next()
}
export default fp(fastifyMongoAuth, {
fastify: '>=2.x',
name: 'fastify-mongo-auth',
decorators: {
fastify: ['httpErrors', 'crud']
},
dependencies: ['@fastify/sensible', 'fastify-mongo-crud']
})