nitro-web 0.0.166 → 0.0.168
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/store.ts +2 -2
- package/components/auth/auth.api.js +128 -93
- package/components/auth/reset.tsx +1 -1
- package/components/auth/signin.tsx +1 -1
- package/components/auth/signup.tsx +1 -1
- package/components/billing/stripe.api.js +2 -2
- package/components/partials/not-found.tsx +6 -2
- package/package.json +1 -1
- package/server/email/index.js +6 -6
- package/server/email/invite-user.html +8 -0
- package/server/index.js +1 -1
- package/server/router.js +47 -31
- package/types/components/auth/auth.api.d.ts +31 -5
- package/types/components/auth/auth.api.d.ts.map +1 -1
- package/types/server/email/index.d.ts +2 -2
- package/types/server/email/index.d.ts.map +1 -1
- package/types/server/index.d.ts +1 -1
- package/types/server/router.d.ts +2 -0
- package/types/server/router.d.ts.map +1 -1
- package/types.ts +1 -1
- package/util.js +2 -2
package/client/store.ts
CHANGED
|
@@ -39,9 +39,9 @@ function beforeUpdate<T extends Store>(newStore: T) {
|
|
|
39
39
|
delete newStore.jwt
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
//
|
|
42
|
+
// Send the requesting user id in the headers for the server to check (see, ./server/router.js:isValidUser() for more details)
|
|
43
43
|
if (newStore?.user?._id) {
|
|
44
|
-
axios().defaults.headers.
|
|
44
|
+
axios().defaults.headers.requestingUserId = newStore?.user?._id
|
|
45
45
|
}
|
|
46
46
|
return newStore
|
|
47
47
|
}
|
|
@@ -19,11 +19,17 @@ export const routes = {
|
|
|
19
19
|
'post /api/signin': [signin],
|
|
20
20
|
'post /api/signup': [signup],
|
|
21
21
|
'post /api/reset-instructions': [resetInstructions],
|
|
22
|
-
'post /api/reset-password': [
|
|
22
|
+
'post /api/reset-password': [resetConfirm],
|
|
23
23
|
'post /api/invite-instructions': [inviteInstructions],
|
|
24
|
-
'post /api/invite-accept': [
|
|
24
|
+
'post /api/invite-accept': [inviteConfirm],
|
|
25
25
|
'delete /api/account/:uid': [remove],
|
|
26
26
|
|
|
27
|
+
// todo:
|
|
28
|
+
// We dont need all of these overridable, just signinAndGetStore, findUserFromProvider,
|
|
29
|
+
// and getStore. So we will allow just these two to be passed around.
|
|
30
|
+
// userCreate not needed, they can just create their own signup function.
|
|
31
|
+
/// Maybe we can pass these into setup?
|
|
32
|
+
|
|
27
33
|
// Overridable helpers
|
|
28
34
|
setup: setup,
|
|
29
35
|
findUserFromProvider: findUserFromProvider,
|
|
@@ -33,14 +39,16 @@ export const routes = {
|
|
|
33
39
|
tokenParse: tokenParse,
|
|
34
40
|
userCreate: userCreate,
|
|
35
41
|
validatePassword: validatePassword,
|
|
42
|
+
sendToken: sendToken,
|
|
43
|
+
inviteOrResetConfirm: inviteOrResetConfirm,
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
function setup(middleware, _config) {
|
|
39
47
|
// routes.setup is called automatically when express starts
|
|
40
48
|
// Set config values
|
|
41
|
-
const configKeys = ['
|
|
49
|
+
const configKeys = ['baseUrl', 'emailFrom', 'env', 'name', 'mailgunDomain', 'mailgunKey', 'masterPassword', 'isNotMultiTenant']
|
|
42
50
|
authConfig = pick(_config, configKeys)
|
|
43
|
-
for (const key of ['
|
|
51
|
+
for (const key of ['baseUrl', 'emailFrom', 'env', 'name']) {
|
|
44
52
|
if (!authConfig[key]) throw new Error(`Missing config value for: config.${key}`)
|
|
45
53
|
}
|
|
46
54
|
|
|
@@ -140,95 +148,6 @@ function signout(req, res) {
|
|
|
140
148
|
res.json('{}')
|
|
141
149
|
}
|
|
142
150
|
|
|
143
|
-
async function resetInstructions(req, res) {
|
|
144
|
-
try {
|
|
145
|
-
let email = (req.body.email || '').trim().toLowerCase()
|
|
146
|
-
if (!email) throw { title: 'email', detail: 'The email you entered is incorrect.' }
|
|
147
|
-
|
|
148
|
-
let user = await db.user.findOne({ query: { email }, _privateData: true })
|
|
149
|
-
if (!user) throw { title: 'email', detail: 'The email you entered is incorrect.' }
|
|
150
|
-
|
|
151
|
-
let resetToken = await tokenCreate(user._id)
|
|
152
|
-
await db.user.update({ query: { email }, $set: { resetToken }})
|
|
153
|
-
|
|
154
|
-
res.json({})
|
|
155
|
-
sendEmail({
|
|
156
|
-
config: authConfig,
|
|
157
|
-
template: 'reset-password',
|
|
158
|
-
to: `${ucFirst(user.firstName)}<${email}>`,
|
|
159
|
-
data: {
|
|
160
|
-
token: resetToken + (req.query.hasOwnProperty('desktop') ? '?desktop' : ''),
|
|
161
|
-
},
|
|
162
|
-
}).catch(err => console.error('sendEmail(..) mailgun error', err))
|
|
163
|
-
} catch (err) {
|
|
164
|
-
res.error(err)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async function resetPassword(req, res) {
|
|
169
|
-
try {
|
|
170
|
-
const { token, password, password2 } = req.body
|
|
171
|
-
const name = req.path.includes('invite') ? 'inviteToken' : 'resetToken'
|
|
172
|
-
const desktop = req.query.desktop
|
|
173
|
-
const id = tokenParse(token)
|
|
174
|
-
await validatePassword(password, password2)
|
|
175
|
-
|
|
176
|
-
let user = await db.user.findOne({ query: id, blacklist: ['-' + name], _privateData: true })
|
|
177
|
-
if (!user || user[name] !== token) throw new Error('Sorry your token is invalid or has already been used.')
|
|
178
|
-
|
|
179
|
-
await db.user.update({
|
|
180
|
-
query: user._id,
|
|
181
|
-
data: {
|
|
182
|
-
password: await bcrypt.hash(password, 10),
|
|
183
|
-
resetToken: '',
|
|
184
|
-
},
|
|
185
|
-
blacklist: ['-' + name, '-password'],
|
|
186
|
-
})
|
|
187
|
-
res.send(await this.signinAndGetStore({ ...user, [name]: undefined }, desktop, this.getStore))
|
|
188
|
-
} catch (err) {
|
|
189
|
-
res.error(err)
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async function inviteInstructions(req, res) {
|
|
194
|
-
try {
|
|
195
|
-
// Check if user is admin here rather than in middleware (which may not exist yet)
|
|
196
|
-
if (req.user.type != 'admin' && !req.user.isAdmin) {
|
|
197
|
-
throw new Error('You are not authorized to invite users.')
|
|
198
|
-
}
|
|
199
|
-
const inviteToken = await tokenCreate()
|
|
200
|
-
const userData = await db.user.validate({
|
|
201
|
-
...pick(req.body, ['email', 'firstName', 'lastName']),
|
|
202
|
-
status: 'invited',
|
|
203
|
-
inviteToken: inviteToken,
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
// Check if user already exists
|
|
207
|
-
if (await db.user.findOne({ query: { email: userData.email } })) {
|
|
208
|
-
throw { title: 'email', detail: 'User already exists.' }
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Create user
|
|
212
|
-
const user = await db.user.insert({
|
|
213
|
-
data: userData,
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
// Send email
|
|
217
|
-
res.send(user)
|
|
218
|
-
sendEmail({
|
|
219
|
-
config: authConfig,
|
|
220
|
-
template: 'invite-user',
|
|
221
|
-
to: `${ucFirst(userData.firstName)}<${userData.email}>`,
|
|
222
|
-
data: {
|
|
223
|
-
token: inviteToken + (req.query.hasOwnProperty('desktop') ? '?desktop' : ''),
|
|
224
|
-
},
|
|
225
|
-
}).catch(err => console.error('sendEmail(..) mailgun error', err))
|
|
226
|
-
|
|
227
|
-
} catch (err) {
|
|
228
|
-
return res.error(err)
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
151
|
async function remove(req, res) {
|
|
233
152
|
try {
|
|
234
153
|
const uid = db.id(req.params.uid || 'badid')
|
|
@@ -409,4 +328,120 @@ export async function validatePassword(password='', password2) {
|
|
|
409
328
|
} else if (typeof password2 != 'undefined' && password !== password2) {
|
|
410
329
|
throw [{ title: 'password2', detail: 'Your passwords need to match.' }]
|
|
411
330
|
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
/* ---- Controllers -------------------------- */
|
|
337
|
+
|
|
338
|
+
export async function resetInstructions(req, res) {
|
|
339
|
+
try {
|
|
340
|
+
// const desktop = req.query.hasOwnProperty('desktop') ? '?desktop' : '' // see sendToken for future usage
|
|
341
|
+
let email = (req.body.email || '').trim().toLowerCase()
|
|
342
|
+
if (!email) throw { title: 'email', detail: 'The email you entered is incorrect.' }
|
|
343
|
+
|
|
344
|
+
let user = await db.user.findOne({ query: { email }, _privateData: true })
|
|
345
|
+
if (!user) throw { title: 'email', detail: 'The email you entered is incorrect.' }
|
|
346
|
+
|
|
347
|
+
// Send reset password email
|
|
348
|
+
await sendToken({ type: 'reset', user: user })
|
|
349
|
+
res.json({})
|
|
350
|
+
} catch (err) {
|
|
351
|
+
res.error(err)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export async function inviteInstructions(req, res) {
|
|
356
|
+
try {
|
|
357
|
+
// const desktop = req.query.hasOwnProperty('desktop') ? '?desktop' : '' // see sendToken for future usage
|
|
358
|
+
let user = await db.user.findOne({ query: { _id: req.params._id }, _privateData: true })
|
|
359
|
+
if (!user) throw new Error('Invalid user id')
|
|
360
|
+
// Send invite instructions email
|
|
361
|
+
await sendToken({ type: 'invite', user: user })
|
|
362
|
+
res.json({})
|
|
363
|
+
} catch (err) {
|
|
364
|
+
res.error(err)
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export async function inviteConfirm(req, res) {
|
|
369
|
+
try {
|
|
370
|
+
res.send(await this.inviteOrResetConfirm('invite', req))
|
|
371
|
+
} catch (err) {
|
|
372
|
+
res.error(err)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export async function resetConfirm(req, res) {
|
|
377
|
+
try {
|
|
378
|
+
res.send(await this.inviteOrResetConfirm('reset', req))
|
|
379
|
+
} catch (err) {
|
|
380
|
+
res.error(err)
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/* ---- Helpers ------------------------------ */
|
|
385
|
+
|
|
386
|
+
export async function inviteOrResetConfirm(type, req) {
|
|
387
|
+
const { token, password, password2 } = req.body
|
|
388
|
+
const name = type === 'invite' ? 'inviteToken' : 'resetToken'
|
|
389
|
+
const desktop = req.query.desktop
|
|
390
|
+
const id = tokenParse(token)
|
|
391
|
+
await validatePassword(password, password2)
|
|
392
|
+
|
|
393
|
+
let user = await db.user.findOne({ query: id, blacklist: ['-' + name], _privateData: true })
|
|
394
|
+
if (!user || user[name] !== token) throw new Error('Sorry your token is invalid or has already been used.')
|
|
395
|
+
|
|
396
|
+
await db.user.update({
|
|
397
|
+
query: user._id,
|
|
398
|
+
data: {
|
|
399
|
+
password: await bcrypt.hash(password, 10),
|
|
400
|
+
[name]: '', // remove token
|
|
401
|
+
},
|
|
402
|
+
blacklist: ['-' + name, '-password'],
|
|
403
|
+
})
|
|
404
|
+
const store = await this.signinAndGetStore({ ...user, [name]: undefined }, desktop, this.getStore)
|
|
405
|
+
return store
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Checks if the user exists, updates the user with the invite token and sends the invite email
|
|
410
|
+
* @param {object} options
|
|
411
|
+
* @param {'reset' | 'invite'} options.type - The type of token to send (default: 'reset')
|
|
412
|
+
* @param {{_id: string, email: string, firstName: string}} options.user - The user to send the invite email to
|
|
413
|
+
* @param {function} [options.beforeUpdate] - callback hook to run before updating the user
|
|
414
|
+
* @param {function} [options.beforeSendEmail] - callback hook to run before sending the email
|
|
415
|
+
* @returns {Promise<{token: string, mailgunPromise: Promise<unknown>}>}
|
|
416
|
+
*/
|
|
417
|
+
export async function sendToken({ type = 'reset', user, beforeUpdate, beforeSendEmail }) {
|
|
418
|
+
if (!user?._id) throw new Error('user is required')
|
|
419
|
+
if (!user?.email) throw new Error('user.email is required')
|
|
420
|
+
if (!user?.firstName) throw new Error('user.firstName is required')
|
|
421
|
+
const token = await tokenCreate(user._id)
|
|
422
|
+
|
|
423
|
+
// Update the user with the token
|
|
424
|
+
const result = await db.user.update({
|
|
425
|
+
query: { _id: user._id },
|
|
426
|
+
data: beforeUpdate ? beforeUpdate({ [type + 'Token']: token }) : { [type + 'Token']: token },
|
|
427
|
+
blacklist: ['-' + type + 'Token'],
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
if (!result._output.matchedCount) {
|
|
431
|
+
throw new Error('Invalid user id to send the token to')
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Send email
|
|
435
|
+
const options = {
|
|
436
|
+
config: authConfig,
|
|
437
|
+
template: type === 'reset' ? 'reset-password' : 'invite-user',
|
|
438
|
+
to: `${ucFirst(user.firstName)}<${user.email}>`,
|
|
439
|
+
data: { token: token }, // + (req.query.hasOwnProperty('desktop') ? '?desktop' : '')
|
|
440
|
+
}
|
|
441
|
+
const mailgunPromise = sendEmail(beforeSendEmail ? beforeSendEmail(options, token) : options).catch(err => {
|
|
442
|
+
console.error('sendEmail(..) mailgun error', err)
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
// Return the token and mailgun promise
|
|
446
|
+
return { token, mailgunPromise }
|
|
412
447
|
}
|
|
@@ -69,7 +69,7 @@ export function ResetPassword({ className, elements, redirectTo }: resetInstruct
|
|
|
69
69
|
try {
|
|
70
70
|
const data = await request('post /api/reset-password', state, event, isLoading, setState)
|
|
71
71
|
setStore((s) => ({ ...s, ...data }))
|
|
72
|
-
navigate(redirectTo || '/')
|
|
72
|
+
setTimeout(() => navigate(redirectTo || '/'), 10) // wait for setStore
|
|
73
73
|
} catch (e) {
|
|
74
74
|
return setState({ ...state, errors: e as Errors })
|
|
75
75
|
}
|
|
@@ -51,7 +51,7 @@ export function Signin({ className, elements, redirectTo }: signinProps) {
|
|
|
51
51
|
setTimeout(() => { // wait for setStore
|
|
52
52
|
if (location.search.includes('redirect')) navigate(location.search.replace('?redirect=', ''))
|
|
53
53
|
else navigate(redirectTo || '/')
|
|
54
|
-
},
|
|
54
|
+
}, 10)
|
|
55
55
|
} catch (e) {
|
|
56
56
|
return setState({ ...state, errors: e as Errors})
|
|
57
57
|
}
|
|
@@ -28,7 +28,7 @@ export function Signup({ className, elements, redirectTo }: signupProps) {
|
|
|
28
28
|
try {
|
|
29
29
|
const data = await request('post /api/signup', state, e, isLoading, setState)
|
|
30
30
|
setStore((prev) => ({ ...prev, ...data }))
|
|
31
|
-
setTimeout(() => navigate(redirectTo || '/'),
|
|
31
|
+
setTimeout(() => navigate(redirectTo || '/'), 10) // wait for setStore
|
|
32
32
|
} catch (e) {
|
|
33
33
|
setState((prev) => ({ ...prev, errors: e as Errors }))
|
|
34
34
|
}
|
|
@@ -21,7 +21,7 @@ function setup(middleware, _config) {
|
|
|
21
21
|
// Set config values
|
|
22
22
|
config = {
|
|
23
23
|
env: _config.env,
|
|
24
|
-
|
|
24
|
+
baseUrl: _config.baseUrl,
|
|
25
25
|
stripeSecretKey: _config.stripeSecretKey,
|
|
26
26
|
stripeWebhookSecret: _config.stripeWebhookSecret,
|
|
27
27
|
}
|
|
@@ -81,7 +81,7 @@ async function billingPortalSessionCreate(req, res) {
|
|
|
81
81
|
}
|
|
82
82
|
const session = await stripe.billingPortal.sessions.create({
|
|
83
83
|
customer: req.user.stripeCustomer.id,
|
|
84
|
-
return_url: config.
|
|
84
|
+
return_url: config.baseUrl + '/subscriptions',
|
|
85
85
|
})
|
|
86
86
|
res.json(session.url)
|
|
87
87
|
} catch (err) {
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
export function NotFound() {
|
|
2
2
|
return (
|
|
3
|
-
<div
|
|
4
|
-
|
|
3
|
+
<div style={{'minHeight': '300px'}}>
|
|
4
|
+
<span class="h1">Page Not Found</span><br />
|
|
5
|
+
<br />
|
|
6
|
+
The page you're looking for doesn't exist or has moved.<br />
|
|
7
|
+
<br />
|
|
8
|
+
<Link to="/">Go back home</Link> or check the URL and try again.
|
|
5
9
|
</div>
|
|
6
10
|
)
|
|
7
11
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitro-web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.168",
|
|
4
4
|
"repository": "github:boycce/nitro-web",
|
|
5
5
|
"homepage": "https://boycce.github.io/nitro-web/",
|
|
6
6
|
"description": "Nitro is a battle-tested, modular base project to turbocharge your projects, styled using Tailwind 🚀",
|
package/server/email/index.js
CHANGED
|
@@ -15,7 +15,7 @@ const _dirname = dirname(fileURLToPath(import.meta.url)) + '/'
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Sends an email using a predefined template, with optional data/or recipientVariables
|
|
18
|
-
* @typedef {{
|
|
18
|
+
* @typedef {{ baseUrl?: string, emailFrom?: string, mailgunDomain?: string, mailgunKey?: string, name?: string }} Config
|
|
19
19
|
*
|
|
20
20
|
* @param {object} opts
|
|
21
21
|
* @param {string} opts.template - Template name or raw HTML, e.g., 'reset-password'
|
|
@@ -46,8 +46,8 @@ export async function sendEmail({
|
|
|
46
46
|
}) {
|
|
47
47
|
if (!config) {
|
|
48
48
|
throw new Error('sendEmail: `config` missing')
|
|
49
|
-
} else if (!config.
|
|
50
|
-
throw new Error('sendEmail: `config.
|
|
49
|
+
} else if (!config.baseUrl) {
|
|
50
|
+
throw new Error('sendEmail: `config.baseUrl` is missing')
|
|
51
51
|
} else if (!config.emailFrom) {
|
|
52
52
|
throw new Error('sendEmail: `config.emailFrom` is missing')
|
|
53
53
|
} else if (!test && (!config.mailgunKey || !config.mailgunDomain)) {
|
|
@@ -81,7 +81,7 @@ export async function sendEmail({
|
|
|
81
81
|
recipientVariables[toEmail] = {
|
|
82
82
|
...(data || {}),
|
|
83
83
|
configName: ucFirst(config.name),
|
|
84
|
-
domain: config.
|
|
84
|
+
domain: config.baseUrl,
|
|
85
85
|
replyToEmail: getNameEmail(replyTo)[1],
|
|
86
86
|
replyToName: getNameEmail(replyTo)[0],
|
|
87
87
|
email: toEmail,
|
|
@@ -95,7 +95,7 @@ export async function sendEmail({
|
|
|
95
95
|
bcc: bcc,
|
|
96
96
|
emailTemplateDir: getDirectories(path, config.pwd).emailTemplateDir,
|
|
97
97
|
from: from,
|
|
98
|
-
isDev: config.
|
|
98
|
+
isDev: config.baseUrl.match(/:/), // possibly use config.env here
|
|
99
99
|
recipientVariables: recipientVariables,
|
|
100
100
|
replyTo: replyTo,
|
|
101
101
|
skipCssInline: skipCssInline,
|
|
@@ -103,7 +103,7 @@ export async function sendEmail({
|
|
|
103
103
|
template: template,
|
|
104
104
|
test: config.emailTestMode || test,
|
|
105
105
|
to: to,
|
|
106
|
-
url: config.
|
|
106
|
+
url: config.baseUrl,
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
// Grab html and send
|
package/server/index.js
CHANGED
|
@@ -18,7 +18,7 @@ async function setupDefaultModels(db) {
|
|
|
18
18
|
export { userModel, companyModel, setupDefaultModels }
|
|
19
19
|
|
|
20
20
|
// Export router
|
|
21
|
-
export { setupRouter, middleware } from './router.js'
|
|
21
|
+
export { setupRouter, middleware, isValidUserOrRespond, isAdminUser } from './router.js'
|
|
22
22
|
|
|
23
23
|
// Export email utility
|
|
24
24
|
export { sendEmail } from './email/index.js'
|
package/server/router.js
CHANGED
|
@@ -374,42 +374,58 @@ export const middleware = {
|
|
|
374
374
|
|
|
375
375
|
|
|
376
376
|
isAdmin: (req, res, next) => {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
let cookieMatch = user && (!req.headers.authid || user._id?.toString() == req.headers.authid)
|
|
381
|
-
if (cookieMatch && user && (user.type?.match(/admin/) || user.isAdmin)) next()
|
|
382
|
-
else if (user && (user.type?.match(/admin/) || user.isAdmin)) res.unauthorized('Invalid cookie, please refresh your browser')
|
|
383
|
-
else if (user) res.unauthorized('You are not authorised to make this request.')
|
|
384
|
-
else res.unauthorized('Please sign in first.')
|
|
377
|
+
if (!isValidUserOrRespond(req, res)) return
|
|
378
|
+
else if (!isAdminUser(req)) res.unauthorized('You are not authorised to make this request.')
|
|
379
|
+
else next()
|
|
385
380
|
},
|
|
386
|
-
// isCompanyOwner: (req, res, next) => {
|
|
387
|
-
// let user = req.user || { companies: [] }
|
|
388
|
-
// let cid = req.params.cid
|
|
389
|
-
// let company = user.companies.find((o) => o._id.toString() == cid)
|
|
390
|
-
// let companyUser = company?.users?.find((o) => o._id.toString() == user._id?.toString())
|
|
391
|
-
// if (!user._id) return res.unauthorized('Please sign in first.')
|
|
392
|
-
// else if (!company || !companyUser) res.unauthorized('You are not authorised to make this request.')
|
|
393
|
-
// else if (companyUser.type != 'owner') res.unauthorized('Only owners can make this request.')
|
|
394
|
-
// else next()
|
|
395
|
-
// },
|
|
396
|
-
// isCompanyUser: (req, res, next) => {
|
|
397
|
-
// let user = req.user || { companies: [] }
|
|
398
|
-
// let cid = req.params.cid
|
|
399
|
-
// let company = user.companies.find((o) => o._id.toString() == cid)
|
|
400
|
-
// if (!user._id) return res.unauthorized('Please sign in first.')
|
|
401
|
-
// else if (!company) res.unauthorized('You are not authorised to make this request.')
|
|
402
|
-
// else next()
|
|
403
|
-
// },
|
|
404
381
|
isUser: (req, res, next) => {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
382
|
+
if (!isValidUserOrRespond(req, res)) return
|
|
383
|
+
else next()
|
|
384
|
+
},
|
|
385
|
+
isParamUser: (req, res, next) => {
|
|
386
|
+
const isParamUser = req.user?._id?.toString() == req.params.uid
|
|
387
|
+
if (!isValidUserOrRespond(req, res)) return
|
|
388
|
+
else if (!isParamUser && !isAdminUser(req)) res.unauthorized('You are not authorised to make this request.')
|
|
389
|
+
else next()
|
|
410
390
|
},
|
|
411
391
|
isDevelopment: (req, res, next) => {
|
|
412
392
|
if (configLocal.env !== 'development') res.error('This API endpoint is only available in development')
|
|
413
393
|
else next()
|
|
414
394
|
},
|
|
395
|
+
isCompanyUser: (req, res, next) => {
|
|
396
|
+
if (!isValidParamCompanyUserOrRespond(req)) return
|
|
397
|
+
else next()
|
|
398
|
+
},
|
|
399
|
+
isCompanyOwner: (req, res, next) => {
|
|
400
|
+
if (!isValidParamCompanyUserOrRespond(req, true)) return
|
|
401
|
+
else next()
|
|
402
|
+
},
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function isAdminUser(req) {
|
|
406
|
+
return (req.user?.type?.match(/admin/) || req.user?.isAdmin) ? true : false
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function isValidUserOrRespond(req, res) {
|
|
410
|
+
// Check if the user is logged in, and that the requesting user is the same as the user, the requesting user might be outdated.
|
|
411
|
+
// E.g. new tab > signout > signin to a different user, now old tab needs to refresh.
|
|
412
|
+
if (!req.user) {
|
|
413
|
+
res.unauthorized('Please sign in first.')
|
|
414
|
+
return false
|
|
415
|
+
} else if (req.headers.requestingUserId && req.user._id?.toString() != req.headers.requestingUserId) {
|
|
416
|
+
res.unauthorized('Invalid session, please refresh your browser.')
|
|
417
|
+
return false
|
|
418
|
+
} else {
|
|
419
|
+
return true
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function isValidParamCompanyUserOrRespond(req, res, checkIsOwner = false) {
|
|
424
|
+
const _company = req.user?.company?._id?.toString() == req.params.cid ? req.user.company : false
|
|
425
|
+
const company = _company || req.user?.companies?.find((o) => o._id.toString() == req.params.cid)
|
|
426
|
+
const isCompanyOwner = company?.users?.find((o) => o._id.toString() == req.user?._id?.toString() && o.type === 'owner')
|
|
427
|
+
if (!isValidUserOrRespond(req, res)) return
|
|
428
|
+
else if (!isAdminUser(req) && !company) res.unauthorized('You are not authorised to make this request.')
|
|
429
|
+
else if (!isAdminUser(req) && checkIsOwner && !isCompanyOwner) res.unauthorized('Only owners can make this request.')
|
|
430
|
+
else return true
|
|
415
431
|
}
|
|
@@ -11,15 +11,42 @@ export function userCreate({ business, password, ...userDataProp }: {
|
|
|
11
11
|
export function tokenCreate(id: any): Promise<any>;
|
|
12
12
|
export function tokenParse(token: any): any;
|
|
13
13
|
export function validatePassword(password: string, password2: any): Promise<void>;
|
|
14
|
+
export function resetInstructions(req: any, res: any): Promise<void>;
|
|
15
|
+
export function inviteInstructions(req: any, res: any): Promise<void>;
|
|
16
|
+
export function inviteConfirm(req: any, res: any): Promise<void>;
|
|
17
|
+
export function resetConfirm(req: any, res: any): Promise<void>;
|
|
18
|
+
export function inviteOrResetConfirm(type: any, req: any): Promise<any>;
|
|
19
|
+
/**
|
|
20
|
+
* Checks if the user exists, updates the user with the invite token and sends the invite email
|
|
21
|
+
* @param {object} options
|
|
22
|
+
* @param {'reset' | 'invite'} options.type - The type of token to send (default: 'reset')
|
|
23
|
+
* @param {{_id: string, email: string, firstName: string}} options.user - The user to send the invite email to
|
|
24
|
+
* @param {function} [options.beforeUpdate] - callback hook to run before updating the user
|
|
25
|
+
* @param {function} [options.beforeSendEmail] - callback hook to run before sending the email
|
|
26
|
+
* @returns {Promise<{token: string, mailgunPromise: Promise<unknown>}>}
|
|
27
|
+
*/
|
|
28
|
+
export function sendToken({ type, user, beforeUpdate, beforeSendEmail }: {
|
|
29
|
+
type: "reset" | "invite";
|
|
30
|
+
user: {
|
|
31
|
+
_id: string;
|
|
32
|
+
email: string;
|
|
33
|
+
firstName: string;
|
|
34
|
+
};
|
|
35
|
+
beforeUpdate?: Function;
|
|
36
|
+
beforeSendEmail?: Function;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
token: string;
|
|
39
|
+
mailgunPromise: Promise<unknown>;
|
|
40
|
+
}>;
|
|
14
41
|
export const routes: {
|
|
15
42
|
'get /api/store': (typeof store)[];
|
|
16
43
|
'get /api/signout': (typeof signout)[];
|
|
17
44
|
'post /api/signin': (typeof signin)[];
|
|
18
45
|
'post /api/signup': (typeof signup)[];
|
|
19
46
|
'post /api/reset-instructions': (typeof resetInstructions)[];
|
|
20
|
-
'post /api/reset-password': (typeof
|
|
47
|
+
'post /api/reset-password': (typeof resetConfirm)[];
|
|
21
48
|
'post /api/invite-instructions': (typeof inviteInstructions)[];
|
|
22
|
-
'post /api/invite-accept': (typeof
|
|
49
|
+
'post /api/invite-accept': (typeof inviteConfirm)[];
|
|
23
50
|
'delete /api/account/:uid': (typeof remove)[];
|
|
24
51
|
setup: typeof setup;
|
|
25
52
|
findUserFromProvider: typeof findUserFromProvider;
|
|
@@ -29,14 +56,13 @@ export const routes: {
|
|
|
29
56
|
tokenParse: typeof tokenParse;
|
|
30
57
|
userCreate: typeof userCreate;
|
|
31
58
|
validatePassword: typeof validatePassword;
|
|
59
|
+
sendToken: typeof sendToken;
|
|
60
|
+
inviteOrResetConfirm: typeof inviteOrResetConfirm;
|
|
32
61
|
};
|
|
33
62
|
declare function store(req: any, res: any): Promise<void>;
|
|
34
63
|
declare function signout(req: any, res: any): void;
|
|
35
64
|
declare function signin(req: any, res: any): any;
|
|
36
65
|
declare function signup(req: any, res: any): Promise<void>;
|
|
37
|
-
declare function resetInstructions(req: any, res: any): Promise<void>;
|
|
38
|
-
declare function resetPassword(req: any, res: any): Promise<void>;
|
|
39
|
-
declare function inviteInstructions(req: any, res: any): Promise<any>;
|
|
40
66
|
declare function remove(req: any, res: any): Promise<void>;
|
|
41
67
|
declare function setup(middleware: any, _config: any): void;
|
|
42
68
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.api.d.ts","sourceRoot":"","sources":["../../../components/auth/auth.api.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.api.d.ts","sourceRoot":"","sources":["../../../components/auth/auth.api.js"],"names":[],"mappings":"AAkLA,qGAyCC;AAED;;GAKC;AAED,0FAQC;AAED;;;;iBAkDC;AAED,mDAOC;AAED,4CAYC;AAED,kFAiBC;AAOD,qEAeC;AAED,sEAWC;AAED,iEAMC;AAED,gEAMC;AAID,wEAoBC;AAED;;;;;;;;GAQG;AACH,yEANG;IAAoC,IAAI,EAAhC,OAAO,GAAG,QAAQ;IACuC,IAAI,EAA7D;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC;IAC5B,YAAY;IACZ,eAAe;CAC1C,GAAU,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAC,CAAC,CAgCtE;AAhbD;;;;;;;;;;;;;;;;;;;;EA6BC;AAmED,0DAEC;AAkCD,mDAEC;AAnBD,iDAeC;AA9BD,2DAaC;AAuBD,2DAwBC;AAjID,4DA+DC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sends an email using a predefined template, with optional data/or recipientVariables
|
|
3
|
-
* @typedef {{
|
|
3
|
+
* @typedef {{ baseUrl?: string, emailFrom?: string, mailgunDomain?: string, mailgunKey?: string, name?: string }} Config
|
|
4
4
|
*
|
|
5
5
|
* @param {object} opts
|
|
6
6
|
* @param {string} opts.template - Template name or raw HTML, e.g., 'reset-password'
|
|
@@ -33,7 +33,7 @@ export function sendEmail({ template, to, config, bcc, data, from, replyTo, reci
|
|
|
33
33
|
* Sends an email using a predefined template, with optional data/or recipientVariables
|
|
34
34
|
*/
|
|
35
35
|
export type Config = {
|
|
36
|
-
|
|
36
|
+
baseUrl?: string;
|
|
37
37
|
emailFrom?: string;
|
|
38
38
|
mailgunDomain?: string;
|
|
39
39
|
mailgunKey?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../server/email/index.js"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;GAiBG;AACH,iIAbG;IAAqB,QAAQ,EAArB,MAAM;IACO,EAAE,EAAf,MAAM;IACO,MAAM,EAAnB,MAAM;IACQ,GAAG,GAAjB,MAAM;IACQ,IAAI,GAAlB,MAAM;IACQ,IAAI,GAAlB,MAAM;IACQ,OAAO,GAArB,MAAM;IACQ,kBAAkB,GAAhC,MAAM;IACQ,OAAO,GAArB,MAAM;IACS,aAAa,GAA5B,OAAO;IACQ,IAAI,GAAnB,OAAO;CACf,GAAU,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAiF/B;;;;qBA/FY;IAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../server/email/index.js"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;GAiBG;AACH,iIAbG;IAAqB,QAAQ,EAArB,MAAM;IACO,EAAE,EAAf,MAAM;IACO,MAAM,EAAnB,MAAM;IACQ,GAAG,GAAjB,MAAM;IACQ,IAAI,GAAlB,MAAM;IACQ,IAAI,GAAlB,MAAM;IACQ,OAAO,GAArB,MAAM;IACQ,kBAAkB,GAAhC,MAAM;IACQ,OAAO,GAArB,MAAM;IACS,aAAa,GAA5B,OAAO;IACQ,IAAI,GAAnB,OAAO;CACf,GAAU,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAiF/B;;;;qBA/FY;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE"}
|
package/types/server/index.d.ts
CHANGED
|
@@ -13,5 +13,5 @@ import userModel from './models/user.js';
|
|
|
13
13
|
import companyModel from './models/company.js';
|
|
14
14
|
export function setupDefaultModels(db: any): Promise<void>;
|
|
15
15
|
export { userModel, companyModel };
|
|
16
|
-
export { setupRouter, middleware } from "./router.js";
|
|
16
|
+
export { setupRouter, middleware, isValidUserOrRespond, isAdminUser } from "./router.js";
|
|
17
17
|
//# sourceMappingURL=index.d.ts.map
|
package/types/server/router.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export function setupRouter(config: any): Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>>;
|
|
2
|
+
export function isAdminUser(req: any): boolean;
|
|
3
|
+
export function isValidUserOrRespond(req: any, res: any): boolean;
|
|
2
4
|
/** @type {MiddlewareConfig} */
|
|
3
5
|
export const middleware: MiddlewareConfig;
|
|
4
6
|
export type Request = express.Request & {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../server/router.js"],"names":[],"mappings":"AAmCA,wHA6HC;
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../server/router.js"],"names":[],"mappings":"AAmCA,wHA6HC;AAoPD,+CAEC;AAED,kEAYC;AApGD,+BAA+B;AAC/B,yBADW,gBAAgB,CAkF1B;sBAnYY,OAAO,CAAC,OAAO,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,GAAoB,CAAC;CAC7B;uBACS,OAAO,CAAC,QAAQ,GAAG;IAC3B,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE,YAAY,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC;IACvD,SAAS,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC;IACpD,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC;IACnD,WAAW,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC;CACvD;+BACS;IACR,KAAK,EAAE,MAAM,EAAE,CAAC;IACpB,CAAK,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,UAAU,KAAK,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC;CACnF;iBA1Ba,MAAM;oBAIH,SAAS"}
|
package/types.ts
CHANGED
package/util.js
CHANGED
|
@@ -103,9 +103,9 @@ export function axios ({ createConfig } = {}) {
|
|
|
103
103
|
if (typeof window !== 'undefined') {
|
|
104
104
|
if (!axiosNonce) {
|
|
105
105
|
// Remove mobile specific protocol and subdomain
|
|
106
|
-
const
|
|
106
|
+
const baseUrl = window.document.location.origin.replace(/^(capacitor|https):\/\/(mobile\.)?/, 'https://')
|
|
107
107
|
axiosNonce = true
|
|
108
|
-
_axios.defaults.baseURL =
|
|
108
|
+
_axios.defaults.baseURL = baseUrl
|
|
109
109
|
_axios.defaults.headers.desktop = true
|
|
110
110
|
_axios.defaults.withCredentials = true
|
|
111
111
|
_axios.defaults.timeout = 60000
|