nitro-web 0.0.183 → 0.0.184

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/app.tsx CHANGED
@@ -319,8 +319,8 @@ async function beforeApp(config: Config) {
319
319
 
320
320
  export const middleware = {
321
321
  // Default middleware that can referenced from component routes
322
- isAdmin: (route: unknown, store: { user?: { type?: string, isAdmin?: boolean } }) => {
323
- if (store.user?.type?.match(/admin/) || store.user?.isAdmin) return
322
+ isAdmin: (route: unknown, store: { user?: { isAdmin?: boolean } }) => {
323
+ if (store.user?.isAdmin) return
324
324
  else if (store.user) return { redirect: '/signin?unauth' }
325
325
  else return { redirect: '/signin?signin' }
326
326
  },
@@ -328,7 +328,7 @@ export const middleware = {
328
328
  if (store.user?.company?.currentSubscription) return
329
329
  else return { redirect: '/plans/subscribe' }
330
330
  },
331
- isUser: (route: unknown, store: { user?: { type?: string } }) => {
331
+ isUser: (route: unknown, store: { user?: { isAdmin?: boolean } }) => {
332
332
  if (store.user) return
333
333
  else return { redirect: '/signin?signin' }
334
334
  },
package/client/index.ts CHANGED
@@ -16,6 +16,7 @@ export { createStore, exposedStoreData, preloadedStoreData, setStoreWrapper } fr
16
16
  export { Signin } from '../components/auth/signin'
17
17
  export { Signup } from '../components/auth/signup'
18
18
  export { ResetInstructions, ResetPassword } from '../components/auth/reset'
19
+ export { InviteConfirm } from '../components/auth/inviteConfirm'
19
20
  export { Dashboard } from '../components/dashboard/dashboard'
20
21
  export { NotFound } from '../components/partials/not-found'
21
22
  export { Styleguide } from '../components/partials/styleguide'
@@ -4,50 +4,46 @@ import bcrypt from 'bcrypt'
4
4
  import passport from 'passport'
5
5
  import passportLocal from 'passport-local'
6
6
  import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt'
7
- import db from 'monastery'
7
+ import db, { isId } from 'monastery'
8
8
  import jsonwebtoken from 'jsonwebtoken'
9
9
  import { sendEmail } from 'nitro-web/server'
10
10
  import { isArray, pick, ucFirst, fullNameSplit } from 'nitro-web/util'
11
+ // Todo: detect if the user is already invited to the company, instead of token error
11
12
 
12
- let authConfig = null
13
13
  const JWT_SECRET = process.env.JWT_SECRET || 'replace_this_with_secure_env_secret'
14
+ let authConfig = null
15
+ let auth = {
16
+ userFindFromProvider, userSigninGetStore, getStore,
17
+ userCreate, passwordValidate, tokenCreate, tokenParse, tokenSend,
18
+ tokenConfirmForReset, tokenConfirmForSingleTenant, tokenConfirmForMultiTenant,
19
+ }
14
20
 
15
21
  export const routes = {
16
22
  // Routes
17
23
  'get /api/store': [store],
18
24
  'get /api/signout': [signout],
19
- 'post /api/signin': [signin],
20
- 'post /api/signup': [signup],
25
+ 'post /api/signin': [signin], // [todo: route gaurd basic limiter]
26
+ 'post /api/signup': [signup], // [todo: route gaurd Altcha]
21
27
  'post /api/reset-instructions': [resetInstructions],
22
- 'post /api/reset-password': [resetConfirm],
28
+ 'post /api/reset-confirm': [resetConfirm], // was reset-password
23
29
  'post /api/invite-instructions': [inviteInstructions],
24
- 'post /api/invite-accept': [inviteConfirm],
30
+ 'post /api/invite-confirm': [inviteConfirm], // was invite-accept
25
31
  'delete /api/account/:uid': [remove],
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
-
33
- // Overridable helpers
32
+ // Setup (called automatically when express starts)
34
33
  setup: setup,
35
- findUserFromProvider: findUserFromProvider,
36
- getStore: getStore,
37
- signinAndGetStore: signinAndGetStore,
38
- tokenCreate: tokenCreate,
39
- tokenParse: tokenParse,
40
- userCreate: userCreate,
41
- validatePassword: validatePassword,
42
- sendToken: sendToken,
43
- inviteOrResetConfirm: inviteOrResetConfirm,
44
34
  }
45
35
 
46
- function setup(middleware, _config) {
47
- // routes.setup is called automatically when express starts
48
- // Set config values
49
- const configKeys = ['baseUrl', 'emailFrom', 'env', 'name', 'mailgunDomain', 'mailgunKey', 'masterPassword', 'isNotMultiTenant']
36
+ function setup(middleware, _config, helpers = {}) {
37
+ // Tip: you can pass in your own helpers to override the default helpers, `this` context contains all helpers.
38
+ // E.g. setup: (middleware, config, helpers) => authRoutes.setup(middleware, config, { getStore, ... })
39
+ const configKeys = ['baseUrl', 'emailFrom', 'env', 'name', 'mailgunDomain', 'mailgunKey', 'masterPassword', 'isNotMultiTenant',
40
+ 'confirmInvites']
50
41
  authConfig = pick(_config, configKeys)
42
+ auth = { ...auth, ...helpers }
43
+ // Bind all the functions to the auth object (so the can be individually overridden and reference other helpers)
44
+ for (const key of Object.keys(auth)) {
45
+ if (typeof auth[key] === 'function') auth[key] = auth[key].bind(auth)
46
+ }
51
47
  for (const key of ['baseUrl', 'emailFrom', 'env', 'name']) {
52
48
  if (!authConfig[key]) throw new Error(`Missing config value for: config.${key}`)
53
49
  }
@@ -57,7 +53,7 @@ function setup(middleware, _config) {
57
53
  { usernameField: 'email' },
58
54
  async (email, password, next) => {
59
55
  try {
60
- const user = await this.findUserFromProvider({ email }, password)
56
+ const user = await auth.userFindFromProvider({ email }, password)
61
57
  next(null, user)
62
58
  } catch (err) {
63
59
  next(err.message)
@@ -74,7 +70,7 @@ function setup(middleware, _config) {
74
70
  },
75
71
  async (payload, done) => {
76
72
  try {
77
- const user = await this.findUserFromProvider({ _id: payload._id })
73
+ const user = await auth.userFindFromProvider({ _id: payload._id })
78
74
  if (!user) return done(null, false)
79
75
  return done(null, user)
80
76
  } catch (err) {
@@ -109,19 +105,14 @@ function setup(middleware, _config) {
109
105
  }
110
106
 
111
107
  async function store(req, res) {
112
- res.json(await this.getStore(req.user))
108
+ res.json(await auth.getStore(req.user))
113
109
  }
114
110
 
115
111
  async function signup(req, res) {
116
112
  try {
117
113
  const desktop = req.query.desktop
118
- let user = await this.userCreate(req.body)
119
- sendEmail({
120
- config: authConfig,
121
- template: 'welcome',
122
- to: `${ucFirst(user.firstName)}<${user.email}>`,
123
- }).catch(console.error)
124
- res.send(await this.signinAndGetStore(user, desktop, this.getStore))
114
+ const user = await auth.userCreate(req.body)
115
+ res.send(await auth.userSigninGetStore(user, desktop))
125
116
  } catch (err) {
126
117
  res.error(err)
127
118
  }
@@ -136,7 +127,7 @@ function signin(req, res) {
136
127
  if (err) return res.error(err)
137
128
  if (!user && info) return res.error('email', info.message)
138
129
  try {
139
- const response = await this.signinAndGetStore(user, desktop, this.getStore)
130
+ const response = await auth.userSigninGetStore(user, desktop)
140
131
  res.send(response)
141
132
  } catch (err) {
142
133
  res.error(err)
@@ -167,18 +158,102 @@ async function remove(req, res) {
167
158
  // }
168
159
  await db.user.remove({ query: { _id: uid }})
169
160
  // Logout now so that an error doesn't throw when naviating to /signout
170
- req.logout()
161
+ await new Promise((resolve, reject) => req.logout(err => err ? reject(err) : resolve()))
171
162
  res.send(`User: '${uid}' removed successfully`)
172
163
  } catch (err) {
173
164
  res.error(err)
174
165
  }
175
166
  }
176
167
 
168
+ export async function resetInstructions(req, res) {
169
+ try {
170
+ // const desktop = req.query.hasOwnProperty('desktop') ? '?desktop' : '' // see sendToken for future usage
171
+ let email = (req.body.email || '').trim().toLowerCase()
172
+ if (!email) throw { title: 'email', detail: 'The email you entered is incorrect.' }
173
+
174
+ let user = await db.user.findOne({ query: { email }, _privateData: true })
175
+ if (!user) throw { title: 'email', detail: 'The email you entered is incorrect.' }
176
+
177
+ // Send reset password email
178
+ await auth.tokenSend({ _id: user._id, email: user.email, firstName: user.firstName })
179
+ res.json({})
180
+ } catch (err) {
181
+ res.error(err)
182
+ }
183
+ }
184
+
185
+ export async function inviteInstructions(req, res) {
186
+ // Single-tenant:
187
+ //. - no user found: error (not supported, must be pre-created)
188
+ // - user pre-created: update user with token, and send them the token
189
+ // Multi-tenant:
190
+ // - user exists and confirmInvites=false: auto-add user to company.users
191
+ // - user exists and confirmInvites=true: add user to company.invites, and send them the token
192
+ //. - no user found: add user to company.invites and send them the token
193
+ try {
194
+ if (authConfig.isNotMultiTenant) {
195
+ const user = await db.user.findOne({ query: { _id: req.body._id }, _privateData: true })
196
+ if (!user) throw new Error('Please pre-create the user first, no user id found.')
197
+ await auth.tokenSend({ type: 'invite', _id: user._id, email: user.email, firstName: user.firstName })
198
+ res.json({})
199
+
200
+ } else {
201
+ const companyId = req.body.companyId
202
+ if (!req.user.isAdmin && (!companyId || req.user?.company?._id?.toString() !== companyId.toString())) {
203
+ throw new Error('You do not have permission to invite users to this company.')
204
+ }
205
+ const existingUser = await db.user.findOne({ query: { email: req.body.email } })
206
+ if (existingUser && !authConfig.confirmInvites) {
207
+ await db.company.update({
208
+ query: db.id(companyId),
209
+ $push: { users: { _id: existingUser._id, role: req.body.role, status: 'active' } },
210
+ })
211
+ } else {
212
+ await auth.tokenSend({
213
+ type: 'companyInvite', _id: companyId,
214
+ email: req.body.email,
215
+ firstName: existingUser?.firstName || req.body.firstName,
216
+ })
217
+ }
218
+ res.json({})
219
+ }
220
+ } catch (err) {
221
+ res.error(err)
222
+ }
223
+ }
224
+
225
+ export async function resetConfirm(req, res) {
226
+ try {
227
+ res.send(await auth.tokenConfirmForReset(req))
228
+ } catch (err) {
229
+ res.error(err)
230
+ }
231
+ }
232
+
233
+ export async function inviteConfirm(req, res) {
234
+ // single tenant:
235
+ // - user pre-created: update user with new password (and any other inviteConfirm.tsx form fields)
236
+ //. - no user found: error (not supported, must be pre-created)
237
+ // multi tenant:
238
+ // - user exists and confirmInvites=false: no-op (i.e no token, user already added)
239
+ // - user exists and confirmInvites=true: update company (invite.tsx should display 'Invite Accepted' and redirect to home)
240
+ // - no user found: create new user with new password (and any other inviteConfirm.tsx form fields)
241
+ try {
242
+ const result = authConfig.isNotMultiTenant
243
+ ? await auth.tokenConfirmForSingleTenant(req)
244
+ : await auth.tokenConfirmForMultiTenant(req)
245
+ res.send(result)
246
+ } catch (err) {
247
+ res.error(err)
248
+ }
249
+ }
250
+
177
251
  /* ---- Overridable helpers ------------------ */
178
252
 
179
- export async function findUserFromProvider(query, passwordToCheck) {
253
+ export async function userFindFromProvider(query, passwordToCheck) {
180
254
  /**
181
- * Find user for state (and verify password if signing in with email)
255
+ * Find user for state (and verify password if signing in with email).
256
+ * NOTE: the application needs to set user.company to the active company (if multi tenant)
182
257
  * @param {object} query - e.g. { email: 'test@test.com' }
183
258
  * @param {string} <passwordToCheck> - password to test
184
259
  */
@@ -219,6 +294,15 @@ export async function findUserFromProvider(query, passwordToCheck) {
219
294
  }
220
295
  }
221
296
 
297
+ export async function userSigninGetStore(user, isDesktop) {
298
+ if (user.loginActive === false) throw { title: 'error', detail: 'This user is not available.' }
299
+ user.desktop = isDesktop
300
+
301
+ const jwt = jsonwebtoken.sign({ _id: user._id }, JWT_SECRET, { expiresIn: '30d' })
302
+ const store = await this.getStore(user)
303
+ return { ...store, jwt }
304
+ }
305
+
222
306
  export async function getStore(user) {
223
307
  // Initial store
224
308
  return {
@@ -226,45 +310,43 @@ export async function getStore(user) {
226
310
  }
227
311
  }
228
312
 
229
- export async function signinAndGetStore(user, isDesktop, getStore) {
230
- if (user.loginActive === false) throw 'This user is not available.'
231
- if (!getStore) throw new Error('Please provide a getStore function')
232
- user.desktop = isDesktop
313
+ /* ---- Helpers (not easily overridable) ----- */
233
314
 
234
- const jwt = jsonwebtoken.sign({ _id: user._id }, JWT_SECRET, { expiresIn: '30d' })
235
- const store = await getStore(user)
236
- return { ...store, jwt }
237
- }
238
-
239
- export async function userCreate({ business, password, ...userDataProp }) {
315
+ /**
316
+ * Creates a new user and company (if multi tenant and `user.company` is not an id)
317
+ * @param {object} userData - user data
318
+ * @param {string} [userData.password] - optional
319
+ * @param {string} [userData.password2] - optional, to confirm the password
320
+ * @param {string} [userData.company] - if multi tenant and `user.company` is not an id, create a new company
321
+ * @param {boolean} [skipSendEmail=false] - whether to skip sending the welcome email
322
+ * @returns {Promise<object>} - the created user
323
+ */
324
+ export async function userCreate({ password, password2, company, ...userDataProp }, skipSendEmail) {
240
325
  try {
241
- if (!this.findUserFromProvider) {
242
- throw new Error('this.findUserFromProvider doesn\'t exist, make sure the context is available when calling this function')
243
- }
244
-
245
- const options = { blacklist: ['-_id'] }
246
- const isMultiTenant = !authConfig.isNotMultiTenant
247
326
  const userId = db.id()
248
- const companyData = isMultiTenant && {
249
- _id: db.id(),
250
- ...(business ? { business } : {}),
327
+ const options = { blacklist: ['-_id'] }
328
+ const companyIsId = !authConfig.isNotMultiTenant && isId(company)
329
+
330
+ // Define new company data if applicable
331
+ const companyData = !authConfig.isNotMultiTenant && !companyIsId && {
332
+ _id: db.id(),
251
333
  users: [{ _id: userId, role: 'owner', status: 'active' }],
334
+ ...(company ? company : {}), // removed
252
335
  }
336
+
337
+ // Define user data
253
338
  const userData = {
254
339
  ...userDataProp,
255
340
  _id: userId,
256
- ...(userDataProp.name ? {
257
- firstName: fullNameSplit(userDataProp.name)[0],
258
- lastName: fullNameSplit(userDataProp.name)[1],
259
- } : {}),
260
341
  password: password ? await bcrypt.hash(password, 10) : undefined,
261
- ...(isMultiTenant ? { company: companyData._id } : {}),
342
+ ...(companyIsId ? { company: company } : (companyData ? { company: companyData._id } : {})), // AKA "active company"
343
+ ...(userDataProp.name ? { firstName: fullNameSplit(userDataProp.name)[0], lastName: fullNameSplit(userDataProp.name)[1] } : {}),
262
344
  }
263
345
  // First validate the data so we don't have to create a transaction
264
346
  const results = await Promise.allSettled([
265
347
  db.user.validate(userData, options),
266
- ...(isMultiTenant ? [db.company.validate(companyData, options)] : []),
267
- typeof password === 'undefined' ? Promise.resolve() : validatePassword(password),
348
+ typeof password === 'undefined' ? Promise.resolve() : this.passwordValidate(password, password2),
349
+ ...(companyData ? [db.company.validate(companyData, options)] : []),
268
350
  ])
269
351
 
270
352
  // Throw all the errors from at once
@@ -277,10 +359,19 @@ export async function userCreate({ business, password, ...userDataProp }) {
277
359
 
278
360
  // Insert company & user
279
361
  await db.user.insert({ data: userData, ...options })
280
- if (isMultiTenant) await db.company.insert({ data: companyData, ...options })
362
+ if (companyData) await db.company.insert({ data: companyData, ...options })
363
+
364
+ // Send welcome email
365
+ if (!skipSendEmail) {
366
+ sendEmail({
367
+ config: authConfig,
368
+ template: 'welcome',
369
+ to: `${ucFirst(userData.firstName)}<${userData.email}>`,
370
+ }).catch(console.error)
371
+ }
281
372
 
282
373
  // Return the user
283
- return await findUserFromProvider({ _id: userId })
374
+ return await this.userFindFromProvider({ _id: userId })
284
375
 
285
376
  } catch (err) {
286
377
  if (!isArray(err)) throw err
@@ -288,30 +379,7 @@ export async function userCreate({ business, password, ...userDataProp }) {
288
379
  }
289
380
  }
290
381
 
291
- export function tokenCreate(id) {
292
- return new Promise((resolve) => {
293
- crypto.randomBytes(16, (err, buff) => {
294
- let hash = buff.toString('hex') // 32 chars
295
- resolve(`${hash}${id || ''}:${Date.now()}`)
296
- })
297
- })
298
- }
299
-
300
- export function tokenParse(token) {
301
- let split = (token || '').split(':')
302
- let hash = split[0].slice(0, 32)
303
- let userId = split[0].slice(32)
304
- let time = split[1]
305
- if (!hash || !userId || !time) {
306
- throw { title: 'error', detail: 'Sorry your code is invalid.' }
307
- } else if (parseFloat(time) + 1000 * 60 * 60 * 24 < Date.now()) {
308
- throw { title: 'error', detail: 'Sorry your code has timed out.' }
309
- } else {
310
- return userId
311
- }
312
- }
313
-
314
- export async function validatePassword(password='', password2) {
382
+ export async function passwordValidate(password='', password2) {
315
383
  // let hasLowerChar = password.match(/[a-z]/)
316
384
  // let hasUpperChar = password.match(/[A-Z]/)
317
385
  // let hasNumber = password.match(/\d/)
@@ -330,117 +398,125 @@ export async function validatePassword(password='', password2) {
330
398
  }
331
399
  }
332
400
 
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
- }
401
+ export function tokenCreate(modelName, id) {
402
+ return new Promise((resolve) => {
403
+ crypto.randomBytes(16, (err, buff) => {
404
+ let hash = buff.toString('hex') // 32 chars
405
+ resolve(`${hash}:${modelName}:${id || ''}:${Date.now()}`)
406
+ })
407
+ })
366
408
  }
367
409
 
368
- export async function inviteConfirm(req, res) {
369
- try {
370
- res.send(await this.inviteOrResetConfirm('invite', req))
371
- } catch (err) {
372
- res.error(err)
410
+ export function tokenParse(token, modelName, maxAgeMs = 1000 * 60 * 60 * 24) {
411
+ let [hash, modelNameParsed, id, time] = (token || '').split(':')
412
+ if (!hash || !id || !time) {
413
+ throw { title: 'error', detail: 'Sorry your code is invalid.' }
414
+ } else if (modelNameParsed !== modelName) {
415
+ throw { title: 'error', detail: 'Sorry we are detecting a token mismatch.' }
416
+ } else if (parseFloat(time) + maxAgeMs < Date.now()) {
417
+ throw { title: 'error', detail: 'Sorry your code has timed out.' }
418
+ } else {
419
+ return id
373
420
  }
374
421
  }
375
422
 
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
- }
423
+ export async function tokenConfirmForReset(req) {
424
+ return await this.tokenConfirmForSingleTenant(req, true)
382
425
  }
383
426
 
384
- /* ---- Helpers ------------------------------ */
385
-
386
- export async function inviteOrResetConfirm(type, req) {
387
- const { token, password, password2 } = req.body
388
- const name = type === 'invite' ? 'inviteToken' : 'resetToken'
427
+ export async function tokenConfirmForSingleTenant(req, isReset) {
428
+ const { token, password, password2, ...userData } = req.body
429
+ const tokenName = isReset ? 'resetToken' : 'inviteToken'
389
430
  const desktop = req.query.desktop
390
- const id = tokenParse(token)
391
- await validatePassword(password, password2)
431
+ const id = this.tokenParse(token, 'user')
432
+ await this.passwordValidate(password, password2)
392
433
 
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.')
434
+ const user = await db.user.findOne({ query: id, blacklist: ['-' + tokenName], _privateData: true })
435
+ if (!user || user[tokenName] !== token) throw new Error('Sorry your token is invalid or has already been used.')
395
436
 
396
437
  await db.user.update({
397
438
  query: user._id,
398
439
  data: {
399
440
  password: await bcrypt.hash(password, 10),
400
- [name]: '', // remove token
441
+ [tokenName]: '', // remove token
442
+ ...userData,
401
443
  },
402
- blacklist: ['-' + name, '-password'],
444
+ blacklist: ['-' + tokenName, '-password'],
403
445
  })
404
- const store = await this.signinAndGetStore({ ...user, [name]: undefined }, desktop, this.getStore)
405
- return store
446
+ return await this.userSigninGetStore({ ...user, [tokenName]: undefined }, desktop)
447
+ }
448
+
449
+ export async function tokenConfirmForMultiTenant(req) {
450
+ const { token, ...userData } = req.body
451
+ const desktop = req.query.desktop
452
+ const companyId = db.id(this.tokenParse(token, 'company'))
453
+
454
+ // Find the invite from the token (company.invites[] entry)
455
+ const company = await db.company.findOne({ query: { _id: companyId, 'invites.inviteToken': token }, _privateData: true })
456
+ if (!company) throw new Error('Sorry your token is invalid or has already been used.')
457
+ const invite = company.invites.find(inv => inv.inviteToken === token)
458
+
459
+ // Has the user already been added to the company (company.users[] entry)?
460
+ const existingUser = await db.user.findOne({ query: { email: userData.email }, _privateData: true })
461
+ if (existingUser && company.users.some(u => u._id.toString() === existingUser._id.toString())) {
462
+ throw new Error('This user has already been added to the company.')
463
+ }
464
+
465
+ // Find or create new user
466
+ const user = existingUser || await this.userCreate({ ...userData, company: companyId }, true) // AKA "active company"
467
+
468
+ // Add the user to the company
469
+ await db.company.update({
470
+ query: companyId,
471
+ $push: { users: { _id: user._id, role: invite.role, status: 'active' } }, // add user to company
472
+ $pull: { invites: { inviteToken: token } }, // remove invite
473
+ })
474
+
475
+ // Signin
476
+ return await this.userSigninGetStore(user, desktop)
406
477
  }
407
478
 
408
479
  /**
409
- * Checks if the user exists, updates the user with the invite token and sends the invite email
480
+ * Creates and sends a reset or invite token to a user or company
410
481
  * @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
482
+ * @param {'reset' | 'invite' | 'companyInvite'} options.type - token type (default: 'reset')
483
+ * @param {string} options._id - user or company id
484
+ * @param {string} options.email - recipient email
485
+ * @param {string} options.firstName - recipient first name
486
+ * @param {function} [options.beforeUpdate] - runs before updating the model with the token, return null to skip update
487
+ * @param {function} [options.beforeSendEmail] - runs before sending the email, receives (options, token)
415
488
  * @returns {Promise<{token: string, mailgunPromise: Promise<unknown>}>}
416
489
  */
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
- // get the data
424
- const data = beforeUpdate ? beforeUpdate({ [type + 'Token']: token }) : { [type + 'Token']: token }
425
- if (type === 'invite') data.isInvited = true
426
-
427
- // Update the user with the token
428
- const result = await db.user.update({
429
- query: { _id: user._id },
430
- data: data,
431
- blacklist: ['-' + type + 'Token'],
432
- })
433
-
434
- if (!result._output.matchedCount) {
435
- throw new Error('Invalid user id to send the token to')
490
+ export async function tokenSend({ type = 'reset', _id, email, firstName, beforeUpdate, beforeSendEmail }) {
491
+ if (!_id) throw new Error(`${type === 'companyInvite' ? 'company' : 'user'} id is required`)
492
+ if (!email) throw new Error('email is required')
493
+ if (!firstName) throw new Error('firstName is required')
494
+
495
+ const tokenName = type === 'companyInvite' ? 'inviteToken' : type + 'Token'
496
+ const modelName = type === 'companyInvite' ? 'company' : 'user'
497
+ const token = await this.tokenCreate(modelName, _id)
498
+ const _beforeUpdate = beforeUpdate || (o => o)
499
+
500
+ if (modelName === 'company') {
501
+ // For companies, add the token to company.invites[].inviteToken
502
+ await db.company.update({ query: db.id(_id), $pull: { invites: { email } } })
503
+ const result = await db.company.update({ query: db.id(_id), $push: { invites: _beforeUpdate({ email: email, inviteToken: token }) }})
504
+ if (!result._output.matchedCount) throw new Error('Invalid company id to update the token for')
505
+ } else {
506
+ // For users, add the token to user.inviteToken|resetToken
507
+ const result = await db[modelName].update({
508
+ query: db.id(_id),
509
+ $set: _beforeUpdate({ [tokenName]: token, isInvited: type === 'invite' ? true : undefined }),
510
+ })
511
+ if (!result._output.matchedCount) throw new Error('Invalid ' + modelName + ' id to update the token for')
436
512
  }
437
513
 
438
514
  // Send email
439
515
  const options = {
440
516
  config: authConfig,
441
- template: type === 'reset' ? 'reset-password' : 'invite-user',
442
- to: `${ucFirst(user.firstName)}<${user.email}>`,
443
- data: { token: token }, // + (req.query.hasOwnProperty('desktop') ? '?desktop' : '')
517
+ template: type === 'reset' ? 'reset-instructions' : 'invite-instructions',
518
+ to: `${ucFirst(firstName)}<${email}>`,
519
+ data: { token: token },
444
520
  }
445
521
  const mailgunPromise = sendEmail(beforeSendEmail ? beforeSendEmail(options, token) : options).catch(err => {
446
522
  console.error('sendEmail(..) mailgun error', err)
@@ -0,0 +1,106 @@
1
+ import { Topbar, Field, FormError, Button, request, onChange, getResponseErrors, showError } from 'nitro-web'
2
+ import { Errors } from 'nitro-web/types'
3
+ import { Fragment, useEffect } from 'react'
4
+
5
+ type InviteConfirmProps = {
6
+ className?: string,
7
+ elements?: { Button?: typeof Button },
8
+ redirectTo?: string,
9
+ }
10
+
11
+ export function InviteConfirm({ className, elements, redirectTo }: InviteConfirmProps) {
12
+ const navigate = useNavigate()
13
+ const params = useParams()
14
+ const [store, setStore] = useTracked()
15
+ const [isLoading, setIsLoading] = useState(false)
16
+ const [accepted, setAccepted] = useState(false)
17
+ const [state, setState] = useState(() => ({
18
+ firstName: '',
19
+ lastName: '',
20
+ password: '',
21
+ password2: '',
22
+ token: params.token,
23
+ errors: [] as Errors,
24
+ }))
25
+
26
+ const Elements = {
27
+ Button: elements?.Button || Button,
28
+ }
29
+
30
+ // Auto-confirm on mount for already signed-in users
31
+ useEffect(() => {
32
+ if (store.user) submit({ token: params.token })
33
+ }, [])
34
+
35
+ async function submit(data: object, event?: React.FormEvent<HTMLFormElement>) {
36
+ try {
37
+ if (isLoading) return
38
+ const result = await request('post /api/invite-confirm', data, event, setIsLoading, setState)
39
+ setStore((s) => ({ ...s, ...result }))
40
+ setAccepted(true)
41
+ setTimeout(() => navigate(redirectTo || '/'), 5000)
42
+ } catch (e) {
43
+ showError(setStore, e)
44
+ setState((s) => ({ ...s, errors: getResponseErrors(e) }))
45
+ }
46
+ }
47
+
48
+ if (store.user) {
49
+ return (
50
+ <div className={className}>
51
+ <div class="py-12 text-center">
52
+ {accepted ? (
53
+ <Fragment>
54
+ <p class="text-lg font-semibold">Your invite has been accepted.</p>
55
+ <p class="text-sm text-gray-500 mt-1">You&apos;ll be redirected back to the <Link to="/">home page</Link> shortly...</p>
56
+ </Fragment>
57
+ ) : isLoading ? (
58
+ <Fragment>
59
+ <p class="text-lg font-semibold">Accepting your invite...</p>
60
+ <p class="text-sm text-gray-500 mt-1">Please wait while we confirm your invite.</p>
61
+ </Fragment>
62
+ ) : (
63
+ <Fragment>
64
+ <p class="text-lg font-semibold mb-2">Oops! Something went wrong.</p>
65
+ <span class="text-sm text-red-500 bg-red-50 p-1 rounded-md mt-1">{state.errors.map((error) => error.detail).join(', ')}</span>
66
+ </Fragment>
67
+ )}
68
+ </div>
69
+ </div>
70
+ )
71
+ }
72
+
73
+ return (
74
+ <div className={className}>
75
+ <Topbar title={<Fragment>Accept Your Invite</Fragment>} />
76
+
77
+ <form onSubmit={(e) => submit(state, e)} class="mb-0">
78
+ <div class="grid grid-cols-2 gap-6">
79
+ <div>
80
+ <label for="firstName">First Name</label>
81
+ <Field name="firstName" type="text" state={state} onChange={(e) => onChange(e, setState)} placeholder="Your first name..." />
82
+ </div>
83
+ <div>
84
+ <label for="lastName">Last Name</label>
85
+ <Field name="lastName" type="text" state={state} onChange={(e) => onChange(e, setState)} placeholder="Your last name..." />
86
+ </div>
87
+ </div>
88
+ <div>
89
+ <label for="password">Choose a Password</label>
90
+ <Field name="password" type="password" state={state} onChange={(e) => onChange(e, setState)} />
91
+ </div>
92
+ <div>
93
+ <label for="password2">Repeat Your Password</label>
94
+ <Field name="password2" type="password" state={state} onChange={(e) => onChange(e, setState)} />
95
+ </div>
96
+
97
+ <div class="mb-14">
98
+ Already have an account? <Link to="/signin" class="underline2 is-active">Sign in here</Link> first then revisit this link.
99
+ <FormError state={state} className="pt-2" />
100
+ </div>
101
+
102
+ <Elements.Button class="w-full" isLoading={isLoading} type="submit">Accept Invite & Create Account</Elements.Button>
103
+ </form>
104
+ </div>
105
+ )
106
+ }
@@ -15,7 +15,7 @@ export function Signup({ className, elements, redirectTo }: signupProps) {
15
15
  const [state, setState] = useState({
16
16
  email: injectedConfig.env === 'development' ? (injectedConfig.placeholderEmail || '') : '',
17
17
  name: injectedConfig.env === 'development' ? 'Bruce Wayne' : '',
18
- business: { name: injectedConfig.env === 'development' ? 'Wayne Enterprises' : '' },
18
+ company: { business: { name: injectedConfig.env === 'development' ? 'Wayne Enterprises' : '' } },
19
19
  password: injectedConfig.env === 'development' ? '' : '',
20
20
  errors: [] as Errors,
21
21
  })
@@ -49,8 +49,11 @@ export function Signup({ className, elements, redirectTo }: signupProps) {
49
49
  />
50
50
  </div>
51
51
  <div>
52
- <label for="business.name">Company Name</label>
53
- <Field name="business.name" placeholder="E.g. Wayne Enterprises" state={state} onChange={(e) => onChange(e, setState)} />
52
+ <label for="company.business.name">Company Name</label>
53
+ <Field name="company.business.name" placeholder="E.g. Wayne Enterprises" state={state}
54
+ onChange={(e) => onChange(e, setState)}
55
+ errorTitle={/business\.name/}
56
+ />
54
57
  </div>
55
58
  </div>
56
59
  <div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-web",
3
- "version": "0.0.183",
3
+ "version": "0.0.184",
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 🚀",
@@ -13,17 +13,20 @@ export default {
13
13
  phone: { type: 'string' },
14
14
  website: { type: 'string', isURL: true },
15
15
  },
16
- status: { type: 'string', default: 'active', enum: ['active', 'unpaid', 'deleted'] },
16
+ status: { type: 'string', default: 'active', enum: ['active', 'unpaid', 'deleted'], required: true },
17
17
  users: [{
18
- _id: { model: 'user' },
19
- role: { type: 'string', enum: ['owner', 'manager', 'accountant'] },
20
- status: { type: 'string', required: true, enum: ['invited', 'active', 'deleted'] },
21
- inviteEmail: { type: 'string' },
22
- inviteToken: { type: 'string' },
18
+ _id: { model: 'user', required: true },
19
+ role: { type: 'string', enum: ['owner', 'manager', 'accountant'], required: true },
20
+ status: { type: 'string', default: 'active', enum: ['active', 'deleted'], required: true },
21
+ }],
22
+ invites: [{
23
+ email: { type: 'email', required: true },
24
+ role: { type: 'string', enum: ['owner', 'manager', 'accountant'], required: true },
25
+ inviteToken: { type: 'string', required: true },
23
26
  }],
24
27
  },
25
28
 
26
- findBL: ['users.inviteToken'],
29
+ findBL: ['invites.token'],
27
30
  updateBL: ['status', 'users'],
28
31
 
29
32
  afterFind: [
@@ -34,8 +37,6 @@ export default {
34
37
  for (let i=data.users.length; i--;) {
35
38
  const user = data.users[i]
36
39
  const userExpanded = data.usersExpanded.find(o => String(o._id) == String(user._id))
37
- // console.log(userExpanded)
38
- if (user.inviteEmail) user.email = user.inviteEmail
39
40
  if (userExpanded) Object.assign(user, { ...userExpanded, name: fullName(userExpanded) })
40
41
  }
41
42
  delete data.usersExpanded
@@ -5,8 +5,9 @@ export default {
5
5
 
6
6
  fields: {
7
7
  avatar: { type: 'image' },
8
- company: { model: 'company', required: true },
8
+ company: { model: 'company', required: true }, // AKA "active company"
9
9
  email: { type: 'email', required: true, index: 'unique' },
10
+ isAdmin: { type: 'boolean', default: false },
10
11
  isInvited: { type: 'boolean' },
11
12
  firstName: { type: 'string', required: true },
12
13
  lastName: { type: 'string', required: true },
@@ -14,7 +15,6 @@ export default {
14
15
  stripeCustomer: { type: 'any' },
15
16
  stripeSubscription: { type: 'any' },
16
17
  stripeIntents: { type: 'any' },
17
- type: { type: 'string', default: 'user', enum: ['user', 'admin'] },
18
18
  usedFreeTrial: { type: 'boolean', default: false },
19
19
  // hidden fields
20
20
  password: { type: 'string', minLength: 6 },
@@ -23,7 +23,7 @@ export default {
23
23
  },
24
24
 
25
25
  findBL: ['password', 'inviteToken', 'resetToken'],
26
- updateBL: ['password', 'inviteToken', 'resetToken', 'company', 'status', 'stripeSubscription', 'type', 'usedFreeTrial'],
26
+ updateBL: ['password', 'inviteToken', 'resetToken', 'company', 'status', 'stripeSubscription', 'isAdmin', 'usedFreeTrial'],
27
27
 
28
28
  messages: {
29
29
  lastName: {
package/server/router.js CHANGED
@@ -404,7 +404,7 @@ export const middleware = {
404
404
  }
405
405
 
406
406
  export function isAdminUser(req) {
407
- return (req.user?.type?.match(/admin/) || req.user?.isAdmin) ? true : false
407
+ return req.user?.isAdmin ? true : false
408
408
  }
409
409
 
410
410
  export function isValidUserOrRespond(req, res) {
@@ -424,7 +424,7 @@ export function isValidUserOrRespond(req, res) {
424
424
  function isValidParamCompanyUserOrRespond(req, res, checkIsOwner = false) {
425
425
  const _company = req.user?.company?._id?.toString() == req.params.cid ? req.user.company : false
426
426
  const company = _company || req.user?.companies?.find((o) => o._id.toString() == req.params.cid)
427
- const isCompanyOwner = company?.users?.find((o) => o._id.toString() == req.user?._id?.toString() && o.type === 'owner')
427
+ const isCompanyOwner = company?.users?.find((o) => o._id.toString() == req.user?._id?.toString() && o.role === 'owner')
428
428
  if (!isValidUserOrRespond(req, res)) return
429
429
  else if (!isAdminUser(req) && !company) res.unauthorized('You are not authorised to make this request.')
430
430
  else if (!isAdminUser(req) && checkIsOwner && !isCompanyOwner) res.unauthorized('Only owners can make this request.')
@@ -1,37 +1,48 @@
1
- export function findUserFromProvider(query: any, passwordToCheck: any, ...args: any[]): Promise<any>;
2
- export function getStore(user: any): Promise<{
3
- user: any;
4
- }>;
5
- export function signinAndGetStore(user: any, isDesktop: any, getStore: any): Promise<any>;
6
- export function userCreate({ business, password, ...userDataProp }: {
7
- [x: string]: any;
8
- business: any;
9
- password: any;
10
- }): Promise<any>;
11
- export function tokenCreate(id: any): Promise<any>;
12
- export function tokenParse(token: any): any;
13
- export function validatePassword(password: string, password2: any): Promise<void>;
14
1
  export function resetInstructions(req: any, res: any): Promise<void>;
15
2
  export function inviteInstructions(req: any, res: any): Promise<void>;
16
- export function inviteConfirm(req: any, res: any): Promise<void>;
17
3
  export function resetConfirm(req: any, res: any): Promise<void>;
18
- export function inviteOrResetConfirm(type: any, req: any): Promise<any>;
4
+ export function inviteConfirm(req: any, res: any): Promise<void>;
5
+ export function userFindFromProvider(query: any, passwordToCheck: any, ...args: any[]): Promise<any>;
6
+ export function userSigninGetStore(user: any, isDesktop: any): Promise<any>;
7
+ export function getStore(user: any): Promise<{
8
+ user: any;
9
+ }>;
10
+ /**
11
+ * Creates a new user and company (if multi tenant and `user.company` is not an id)
12
+ * @param {object} userData - user data
13
+ * @param {string} [userData.password] - optional
14
+ * @param {string} [userData.password2] - optional, to confirm the password
15
+ * @param {string} [userData.company] - if multi tenant and `user.company` is not an id, create a new company
16
+ * @param {boolean} [skipSendEmail=false] - whether to skip sending the welcome email
17
+ * @returns {Promise<object>} - the created user
18
+ */
19
+ export function userCreate({ password, password2, company, ...userDataProp }: {
20
+ password?: string;
21
+ password2?: string;
22
+ company?: string;
23
+ }, skipSendEmail?: boolean): Promise<object>;
24
+ export function passwordValidate(password: string, password2: any): Promise<void>;
25
+ export function tokenCreate(modelName: any, id: any): Promise<any>;
26
+ export function tokenParse(token: any, modelName: any, maxAgeMs?: number): any;
27
+ export function tokenConfirmForReset(req: any): Promise<any>;
28
+ export function tokenConfirmForSingleTenant(req: any, isReset: any): Promise<any>;
29
+ export function tokenConfirmForMultiTenant(req: any): Promise<any>;
19
30
  /**
20
- * Checks if the user exists, updates the user with the invite token and sends the invite email
31
+ * Creates and sends a reset or invite token to a user or company
21
32
  * @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
33
+ * @param {'reset' | 'invite' | 'companyInvite'} options.type - token type (default: 'reset')
34
+ * @param {string} options._id - user or company id
35
+ * @param {string} options.email - recipient email
36
+ * @param {string} options.firstName - recipient first name
37
+ * @param {function} [options.beforeUpdate] - runs before updating the model with the token, return null to skip update
38
+ * @param {function} [options.beforeSendEmail] - runs before sending the email, receives (options, token)
26
39
  * @returns {Promise<{token: string, mailgunPromise: Promise<unknown>}>}
27
40
  */
28
- export function sendToken({ type, user, beforeUpdate, beforeSendEmail }: {
29
- type: "reset" | "invite";
30
- user: {
31
- _id: string;
32
- email: string;
33
- firstName: string;
34
- };
41
+ export function tokenSend({ type, _id, email, firstName, beforeUpdate, beforeSendEmail }: {
42
+ type: "reset" | "invite" | "companyInvite";
43
+ _id: string;
44
+ email: string;
45
+ firstName: string;
35
46
  beforeUpdate?: Function;
36
47
  beforeSendEmail?: Function;
37
48
  }): Promise<{
@@ -44,26 +55,17 @@ export const routes: {
44
55
  'post /api/signin': (typeof signin)[];
45
56
  'post /api/signup': (typeof signup)[];
46
57
  'post /api/reset-instructions': (typeof resetInstructions)[];
47
- 'post /api/reset-password': (typeof resetConfirm)[];
58
+ 'post /api/reset-confirm': (typeof resetConfirm)[];
48
59
  'post /api/invite-instructions': (typeof inviteInstructions)[];
49
- 'post /api/invite-accept': (typeof inviteConfirm)[];
60
+ 'post /api/invite-confirm': (typeof inviteConfirm)[];
50
61
  'delete /api/account/:uid': (typeof remove)[];
51
62
  setup: typeof setup;
52
- findUserFromProvider: typeof findUserFromProvider;
53
- getStore: typeof getStore;
54
- signinAndGetStore: typeof signinAndGetStore;
55
- tokenCreate: typeof tokenCreate;
56
- tokenParse: typeof tokenParse;
57
- userCreate: typeof userCreate;
58
- validatePassword: typeof validatePassword;
59
- sendToken: typeof sendToken;
60
- inviteOrResetConfirm: typeof inviteOrResetConfirm;
61
63
  };
62
64
  declare function store(req: any, res: any): Promise<void>;
63
65
  declare function signout(req: any, res: any): void;
64
66
  declare function signin(req: any, res: any): any;
65
67
  declare function signup(req: any, res: any): Promise<void>;
66
68
  declare function remove(req: any, res: any): Promise<void>;
67
- declare function setup(middleware: any, _config: any): void;
69
+ declare function setup(middleware: any, _config: any, helpers?: {}): void;
68
70
  export {};
69
71
  //# sourceMappingURL=auth.api.d.ts.map
@@ -1 +1 @@
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,CAoCtE;AApbD;;;;;;;;;;;;;;;;;;;;EA6BC;AAmED,0DAEC;AAkCD,mDAEC;AAnBD,iDAeC;AA9BD,2DAaC;AAuBD,2DAwBC;AAjID,4DA+DC"}
1
+ {"version":3,"file":"auth.api.d.ts","sourceRoot":"","sources":["../../../components/auth/auth.api.js"],"names":[],"mappings":"AAuKA,qEAeC;AAED,sEAsCC;AAED,gEAMC;AAED,iEAgBC;AAID,qGA0CC;AAED,4EAOC;AAED;;GAKC;AAID;;;;;;;;GAQG;AACH,8EANG;IAA0B,QAAQ,GAA1B,MAAM;IACY,SAAS,GAA3B,MAAM;IACY,OAAO,GAAzB,MAAM;CACd,kBAAQ,OAAO,GACL,OAAO,CAAC,MAAM,CAAC,CA0D3B;AAED,kFAiBC;AAED,mEAOC;AAED,+EAWC;AAED,6DAEC;AAED,kFAoBC;AAED,mEA4BC;AAED;;;;;;;;;;GAUG;AACH,0FARG;IAAsD,IAAI,EAAlD,OAAO,GAAG,QAAQ,GAAG,eAAe;IACpB,GAAG,EAAnB,MAAM;IACU,KAAK,EAArB,MAAM;IACU,SAAS,EAAzB,MAAM;IACa,YAAY;IACZ,eAAe;CAC1C,GAAU,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAC,CAAC,CAuCtE;AA1fD;;;;;;;;;;;EAaC;AAyED,0DAEC;AA6BD,mDAEC;AAnBD,iDAeC;AAzBD,2DAQC;AAuBD,2DAwBC;AAlID,0EAqEC"}
@@ -90,25 +90,39 @@ declare namespace _default {
90
90
  export { _default_2 as default };
91
91
  let _enum: string[];
92
92
  export { _enum as enum };
93
+ let required_3: boolean;
94
+ export { required_3 as required };
93
95
  }
94
96
  let users: {
95
97
  _id: {
96
98
  model: string;
99
+ required: boolean;
97
100
  };
98
101
  role: {
99
102
  type: string;
100
103
  enum: string[];
104
+ required: boolean;
101
105
  };
102
106
  status: {
103
107
  type: string;
104
- required: boolean;
108
+ default: string;
105
109
  enum: string[];
110
+ required: boolean;
111
+ };
112
+ }[];
113
+ let invites: {
114
+ email: {
115
+ type: string;
116
+ required: boolean;
106
117
  };
107
- inviteEmail: {
118
+ role: {
108
119
  type: string;
120
+ enum: string[];
121
+ required: boolean;
109
122
  };
110
123
  inviteToken: {
111
124
  type: string;
125
+ required: boolean;
112
126
  };
113
127
  }[];
114
128
  }
@@ -1 +1 @@
1
- {"version":3,"file":"company.d.ts","sourceRoot":"","sources":["../../../server/models/company.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA8CgB,sCAEX;QACc;;;;;;;;;;;;;;;;;;;;;;;YAyBd"}
1
+ {"version":3,"file":"company.d.ts","sourceRoot":"","sources":["../../../server/models/company.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA+CgB,sCAEX;QACc;;;;;;;;;;;;;;;;;;;;;;;YAyBd"}
@@ -1,83 +1,80 @@
1
1
  declare namespace _default {
2
2
  namespace fields {
3
- export namespace avatar {
3
+ namespace avatar {
4
4
  let type: string;
5
5
  }
6
- export namespace company {
6
+ namespace company {
7
7
  let model: string;
8
8
  let required: boolean;
9
9
  }
10
- export namespace email {
10
+ namespace email {
11
11
  let type_1: string;
12
12
  export { type_1 as type };
13
13
  let required_1: boolean;
14
14
  export { required_1 as required };
15
15
  export let index: string;
16
16
  }
17
- export namespace isInvited {
17
+ namespace isAdmin {
18
18
  let type_2: string;
19
19
  export { type_2 as type };
20
+ let _default: boolean;
21
+ export { _default as default };
20
22
  }
21
- export namespace firstName {
23
+ namespace isInvited {
22
24
  let type_3: string;
23
25
  export { type_3 as type };
24
- let required_2: boolean;
25
- export { required_2 as required };
26
26
  }
27
- export namespace lastName {
27
+ namespace firstName {
28
28
  let type_4: string;
29
29
  export { type_4 as type };
30
- let required_3: boolean;
31
- export { required_3 as required };
30
+ let required_2: boolean;
31
+ export { required_2 as required };
32
32
  }
33
- export namespace status {
33
+ namespace lastName {
34
34
  let type_5: string;
35
35
  export { type_5 as type };
36
- let _default: string;
37
- export { _default as default };
38
- let _enum: string[];
39
- export { _enum as enum };
36
+ let required_3: boolean;
37
+ export { required_3 as required };
40
38
  }
41
- export namespace stripeCustomer {
39
+ namespace status {
42
40
  let type_6: string;
43
41
  export { type_6 as type };
42
+ let _default_1: string;
43
+ export { _default_1 as default };
44
+ let _enum: string[];
45
+ export { _enum as enum };
44
46
  }
45
- export namespace stripeSubscription {
47
+ namespace stripeCustomer {
46
48
  let type_7: string;
47
49
  export { type_7 as type };
48
50
  }
49
- export namespace stripeIntents {
51
+ namespace stripeSubscription {
50
52
  let type_8: string;
51
53
  export { type_8 as type };
52
54
  }
53
- export namespace type_9 {
55
+ namespace stripeIntents {
56
+ let type_9: string;
57
+ export { type_9 as type };
58
+ }
59
+ namespace usedFreeTrial {
54
60
  let type_10: string;
55
61
  export { type_10 as type };
56
- let _default_1: string;
57
- export { _default_1 as default };
58
- let _enum_1: string[];
59
- export { _enum_1 as enum };
62
+ let _default_2: boolean;
63
+ export { _default_2 as default };
60
64
  }
61
- export { type_9 as type };
62
- export namespace usedFreeTrial {
65
+ namespace password {
63
66
  let type_11: string;
64
67
  export { type_11 as type };
65
- let _default_2: boolean;
66
- export { _default_2 as default };
68
+ export let minLength: number;
67
69
  }
68
- export namespace password {
70
+ namespace inviteToken {
69
71
  let type_12: string;
70
72
  export { type_12 as type };
71
- export let minLength: number;
72
73
  }
73
- export namespace inviteToken {
74
+ namespace resetToken {
74
75
  let type_13: string;
75
76
  export { type_13 as type };
76
77
  }
77
- export namespace resetToken {
78
- let type_14: string;
79
- export { type_14 as type };
80
- }
81
78
  }
82
79
  let findBL: string[];
83
80
  let updateBL: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../../server/models/user.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAiDmB,gCAEd"}
1
+ {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../../server/models/user.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAiDmB,gCAEd"}
package/types.ts CHANGED
@@ -24,13 +24,13 @@ export type Config = InjectedConfig & {
24
24
 
25
25
  export type User = {
26
26
  _id?: string
27
+ email?: string
27
28
  firstName?: string
28
29
  lastName?: string
29
30
  name?: string
30
31
  avatar?: MonasteryImage
31
32
  isAdmin?: boolean
32
33
  isInvited?: boolean
33
- type?: string
34
34
  }
35
35
 
36
36
  export type Error = { title: string, detail: string }