nitro-web 0.1.4 → 0.2.0

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.
@@ -1,25 +1,29 @@
1
- import { Topbar, Field, FormError, Button, request, onChange, getResponseErrors, showError } from 'nitro-web'
2
- import { Errors } from 'nitro-web/types'
1
+ import { Topbar, Field, FormError, Button, request, onChange, getResponseErrors, getSignoutStore, getInitialStore } from 'nitro-web'
2
+ import { Config, Errors } from 'nitro-web/types'
3
+ import { twMerge } from 'nitro-web/util'
3
4
  import { Fragment, useEffect } from 'react'
4
5
 
5
6
  type InviteConfirmProps = {
6
7
  className?: string,
7
8
  elements?: { Button?: typeof Button, Header?: React.ReactNode },
8
9
  redirectTo?: string,
10
+ config: Pick<Config, 'getSignoutStore'>
9
11
  }
10
12
 
11
- export function InviteConfirm({ className, elements, redirectTo }: InviteConfirmProps) {
13
+ export function InviteConfirm({ className, elements, redirectTo, config }: InviteConfirmProps) {
12
14
  const navigate = useNavigate()
13
15
  const params = useParams()
14
- const [store, setStore] = useTracked()
16
+ const [, setStore] = useTracked()
17
+ const getSignoutStoreFn = config.getSignoutStore || getSignoutStore
15
18
  const [isLoading, setIsLoading] = useState(false)
16
- const [accepted, setAccepted] = useState(false)
19
+ const [isExistingUser, setIsExistingUser] = useState<boolean | 'pending'>('pending')
20
+ const [isAccepted, setIsAccepted] = useState(false)
17
21
  const [state, setState] = useState(() => ({
18
22
  firstName: '',
19
23
  lastName: '',
20
24
  password: '',
21
25
  password2: '',
22
- token: params.token,
26
+ email: '',
23
27
  errors: [] as Errors,
24
28
  }))
25
29
 
@@ -28,45 +32,56 @@ export function InviteConfirm({ className, elements, redirectTo }: InviteConfirm
28
32
  Header: elements?.Header || null,
29
33
  }
30
34
 
31
- // Auto-confirm on mount for already signed-in users
35
+ // Get invite details on mount
32
36
  useEffect(() => {
33
- if (store.user) submit({ token: params.token })
37
+ preSubmit()
34
38
  }, [])
35
39
 
40
+ async function preSubmit() {
41
+ try {
42
+ const result = await request(`get /api/invite-pre-confirm/${params.token}`)
43
+ setIsExistingUser(result.isExistingUser)
44
+ setState((s) => ({ ...s, email: result.email }))
45
+ if (result.isExistingUser) submit({ token: params.token })
46
+ } catch (e) {
47
+ setState((s) => ({ ...s, errors: getResponseErrors(e) }))
48
+ }
49
+ }
50
+
36
51
  async function submit(data: object, event?: React.FormEvent<HTMLFormElement>) {
37
52
  try {
38
53
  if (isLoading) return
39
- const result = await request('post /api/invite-confirm', data, event, setIsLoading, setState)
40
- setStore((s) => ({ ...s, ...result }))
41
- setAccepted(true)
54
+ const result = await request(`post /api/invite-confirm/${params.token}`, data, event, setIsLoading, setState)
55
+ // Only update the store if the user was created AND refreshly signed in
56
+ if (result?.jwt) setStore((s) => ({ ...getSignoutStoreFn(s, getInitialStore()), ...result }))
57
+ setIsAccepted(true)
42
58
  setTimeout(() => navigate(redirectTo || '/'), 5000)
43
59
  } catch (e) {
44
- showError(setStore, e)
45
60
  setState((s) => ({ ...s, errors: getResponseErrors(e) }))
46
61
  }
47
62
  }
48
63
 
49
- if (store.user) {
64
+ if (isExistingUser || isAccepted) {
50
65
  return (
51
- <div className={className}>
52
- <div class="py-12 text-center">
53
- {accepted ? (
54
- <Fragment>
55
- <p class="text-lg font-semibold">Your invite has been accepted.</p>
56
- <p class="text-sm text-gray-500 mt-1">You&apos;ll be redirected back to the <Link to="/">home page</Link> shortly...</p>
57
- </Fragment>
58
- ) : isLoading ? (
59
- <Fragment>
60
- <p class="text-lg font-semibold">Accepting your invite...</p>
61
- <p class="text-sm text-gray-500 mt-1">Please wait while we confirm your invite.</p>
62
- </Fragment>
63
- ) : (
64
- <Fragment>
65
- <p class="text-lg font-semibold mb-2">Oops! Something went wrong.</p>
66
- <span class="text-sm text-red-500 bg-red-50 p-1 rounded-md mt-1">{state.errors.map((error) => error.detail).join(', ')}</span>
67
- </Fragment>
68
- )}
69
- </div>
66
+ <div className={twMerge('min-h-[250px]', className)}>
67
+ {isAccepted ? (
68
+ <Fragment>
69
+ <div class="text-2xl font-bold mb-4">Your invite has been accepted.</div>
70
+ <p class="">You&apos;ll be redirected back to the <Link to="/">home page</Link> shortly...</p>
71
+ </Fragment>
72
+ ) : isExistingUser === 'pending' && !state.errors.length ? (
73
+ <Fragment>
74
+ <div class="text-2xl font-bold mb-4">One moment please...</div>
75
+ <p class="">Verifying your token.</p>
76
+ </Fragment>
77
+ ) : (
78
+ <Fragment>
79
+ <div class="text-2xl font-bold mb-4">Something went wrong.</div>
80
+ {state.errors.map((error, i) => {
81
+ return (<span key={i} class="text-red-500 bg-red-50 p-1 rounded-md">{error.detail} <br /></span>)
82
+ })}
83
+ </Fragment>
84
+ )}
70
85
  </div>
71
86
  )
72
87
  }
@@ -74,19 +89,25 @@ export function InviteConfirm({ className, elements, redirectTo }: InviteConfirm
74
89
  return (
75
90
  <div className={className}>
76
91
  {!!Elements.Header && Elements.Header}
77
- <Topbar title={<Fragment>Accept Your Invite</Fragment>} />
92
+ <Topbar title={<Fragment>Accept Invitation</Fragment>} />
78
93
 
79
94
  <form onSubmit={(e) => submit(state, e)} class="mb-0">
80
95
  <div class="grid grid-cols-2 gap-6">
81
96
  <div>
82
97
  <label for="firstName">First Name</label>
83
- <Field name="firstName" type="text" state={state} onChange={(e) => onChange(e, setState)} placeholder="Your first name..." />
98
+ <Field name="firstName" type="text" state={state} onChange={(e) => onChange(e, setState)} placeholder="Your first name..."
99
+ autoComplete="given-name" />
84
100
  </div>
85
101
  <div>
86
102
  <label for="lastName">Last Name</label>
87
- <Field name="lastName" type="text" state={state} onChange={(e) => onChange(e, setState)} placeholder="Your last name..." />
103
+ <Field name="lastName" type="text" state={state} onChange={(e) => onChange(e, setState)} placeholder="Your last name..."
104
+ autoComplete="off" />
88
105
  </div>
89
106
  </div>
107
+ <div>
108
+ <label for="email">Email Address</label>
109
+ <Field name="email" type="email" state={state} placeholder="Your email address..." disabled={true} />
110
+ </div>
90
111
  <div>
91
112
  <label for="password">Choose a Password</label>
92
113
  <Field name="password" type="password" state={state} onChange={(e) => onChange(e, setState)} />
@@ -97,8 +118,7 @@ export function InviteConfirm({ className, elements, redirectTo }: InviteConfirm
97
118
  </div>
98
119
 
99
120
  <div class="mb-14">
100
- Already have an account? <Link to="/signin" class="underline2 is-active">Sign in here</Link> first then revisit this link.
101
- <FormError state={state} className="pt-2" />
121
+ <FormError state={state} className="pt-2" fields={['firstName', 'lastName', 'password', 'password2']} />
102
122
  </div>
103
123
 
104
124
  <Elements.Button class="w-full" isLoading={isLoading} type="submit">Accept Invite & Create Account</Elements.Button>
@@ -1,5 +1,5 @@
1
- import { Topbar, Field, FormError, Button, request, onChange } from 'nitro-web'
2
- import { Errors } from 'nitro-web/types'
1
+ import { Topbar, Field, FormError, Button, request, onChange, getSignoutStore, getInitialStore } from 'nitro-web'
2
+ import { Config, Errors } from 'nitro-web/types'
3
3
  import { Fragment } from 'react'
4
4
 
5
5
  type resetInstructionsProps = {
@@ -8,6 +8,10 @@ type resetInstructionsProps = {
8
8
  redirectTo?: string,
9
9
  }
10
10
 
11
+ type resetPasswordProps = resetInstructionsProps & {
12
+ config: Pick<Config, 'getSignoutStore'>
13
+ }
14
+
11
15
  export function ResetInstructions({ className, elements, redirectTo }: resetInstructionsProps) {
12
16
  const navigate = useNavigate()
13
17
  const [isLoading, setIsLoading] = useState(false)
@@ -52,9 +56,10 @@ export function ResetInstructions({ className, elements, redirectTo }: resetInst
52
56
  )
53
57
  }
54
58
 
55
- export function ResetPassword({ className, elements, redirectTo }: resetInstructionsProps) {
59
+ export function ResetPassword({ className, elements, redirectTo, config }: resetPasswordProps) {
56
60
  const navigate = useNavigate()
57
61
  const params = useParams()
62
+ const getSignoutStoreFn = config.getSignoutStore || getSignoutStore
58
63
  const [isLoading, setIsLoading] = useState(false)
59
64
  const [, setStore] = useTracked()
60
65
  const [state, setState] = useState(() => ({
@@ -73,7 +78,7 @@ export function ResetPassword({ className, elements, redirectTo }: resetInstruct
73
78
  try {
74
79
  if (isLoading) return
75
80
  const data = await request('post /api/reset-password', state, event, setIsLoading, setState)
76
- setStore((s) => ({ ...s, ...data }))
81
+ setStore((s) => ({ ...getSignoutStoreFn(s, getInitialStore()), ...data }))
77
82
  setTimeout(() => navigate(redirectTo || '/'), 10) // wait for setStore
78
83
  } catch (e) {
79
84
  return setState({ ...state, errors: e as Errors })
@@ -1,5 +1,8 @@
1
- import { Topbar, Field, Button, FormError, request, queryObject, injectedConfig, updateJwt, onChange } from 'nitro-web'
2
- import { Errors } from 'nitro-web/types'
1
+ import {
2
+ Topbar, Field, Button, FormError, request, queryObject, injectedConfig, updateJwt, onChange,
3
+ getSignoutStore, getInitialStore,
4
+ } from 'nitro-web'
5
+ import { Config, Errors } from 'nitro-web/types'
3
6
  import { Fragment } from 'react'
4
7
 
5
8
  type signinProps = {
@@ -7,12 +10,14 @@ type signinProps = {
7
10
  elements?: { Button?: typeof Button, Header?: React.ReactNode },
8
11
  redirectTo?: string,
9
12
  hideSignup?: boolean,
13
+ config: Pick<Config, 'getSignoutStore'>
10
14
  }
11
15
 
12
- export function Signin({ className, elements, redirectTo, hideSignup }: signinProps) {
16
+ export function Signin({ className, elements, redirectTo, hideSignup, config }: signinProps) {
13
17
  const navigate = useNavigate()
14
18
  const location = useLocation()
15
19
  const isSignout = location.pathname == '/signout'
20
+ const getSignoutStoreFn = config.getSignoutStore || getSignoutStore
16
21
  const [isLoading, setIsLoading] = useState(isSignout)
17
22
  const [, setStore] = useTracked()
18
23
  const [state, setState] = useState({
@@ -32,9 +37,11 @@ export function Signin({ className, elements, redirectTo, hideSignup }: signinPr
32
37
  if (query.email) setState({ ...state, email: query.email as string })
33
38
  }, [location.search])
34
39
 
40
+
35
41
  useEffect(() => {
36
42
  if (isSignout) {
37
- setStore((s) => ({ ...s, user: undefined }))
43
+ // Reset the user to the initialStoreData user
44
+ setStore((s) => getSignoutStoreFn(s, getInitialStore()))
38
45
  // util.axios().get('/api/signout')
39
46
  Promise.resolve()
40
47
  .then(() => setIsLoading(false))
@@ -50,7 +57,7 @@ export function Signin({ className, elements, redirectTo, hideSignup }: signinPr
50
57
  const data = await request('post /api/signin', state, e, setIsLoading, setState)
51
58
  // Keep it loading until we navigate
52
59
  setIsLoading(true)
53
- setStore((s) => ({ ...s, ...data }))
60
+ setStore((s) => ({ ...getSignoutStoreFn(s, getInitialStore()), ...data }))
54
61
  setTimeout(() => { // wait for setStore
55
62
  if (location.search.includes('redirect')) navigate(location.search.replace('?redirect=', ''))
56
63
  else navigate(redirectTo || '/')
@@ -1,17 +1,19 @@
1
- import { Button, Field, FormError, Topbar, request, injectedConfig, onChange } from 'nitro-web'
2
- import { Errors } from 'nitro-web/types'
1
+ import { Button, Field, FormError, Topbar, request, injectedConfig, onChange, getSignoutStore, getInitialStore } from 'nitro-web'
2
+ import { Config, Errors } from 'nitro-web/types'
3
3
  import { Fragment } from 'react'
4
4
 
5
5
  type signupProps = {
6
6
  className?: string,
7
7
  elements?: { Button?: typeof Button, Header?: React.ReactNode },
8
8
  redirectTo?: string,
9
+ config: Pick<Config, 'getSignoutStore'>
9
10
  }
10
11
 
11
- export function Signup({ className, elements, redirectTo }: signupProps) {
12
+ export function Signup({ className, elements, redirectTo, config }: signupProps) {
12
13
  const navigate = useNavigate()
13
14
  const [isLoading, setIsLoading] = useState(false)
14
15
  const [, setStore] = useTracked()
16
+ const getSignoutStoreFn = config.getSignoutStore || getSignoutStore
15
17
  const [state, setState] = useState({
16
18
  email: injectedConfig.env === 'development' ? (injectedConfig.placeholderEmail || '') : '',
17
19
  name: injectedConfig.env === 'development' ? 'Bruce Wayne' : '',
@@ -29,7 +31,7 @@ export function Signup({ className, elements, redirectTo }: signupProps) {
29
31
  try {
30
32
  if (isLoading) return
31
33
  const data = await request('post /api/signup', state, e, setIsLoading, setState)
32
- setStore((prev) => ({ ...prev, ...data }))
34
+ setStore((s) => ({ ...getSignoutStoreFn(s, getInitialStore()), ...data }))
33
35
  setTimeout(() => navigate(redirectTo || '/'), 10) // wait for setStore
34
36
  } catch (e) {
35
37
  setState((prev) => ({ ...prev, errors: e as Errors }))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-web",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
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,6 +13,10 @@ let templates = {}
13
13
  let nodemailerMailgun = undefined
14
14
  const _dirname = dirname(fileURLToPath(import.meta.url)) + '/'
15
15
 
16
+ export const requiredEmailConfigKeys = ['baseUrl', 'emailFrom', 'name', 'env']
17
+ export const optionalEmailConfigKeys = ['emailReplyTo', 'emailTestMode', 'mailgunDomain', 'mailgunKey']
18
+
19
+
16
20
  /**
17
21
  * Sends an email using a predefined template, with optional data/or recipientVariables
18
22
  * @typedef {{ baseUrl?: string, emailFrom?: string, mailgunDomain?: string, mailgunKey?: string, name?: string }} Config
@@ -44,24 +48,19 @@ export async function sendEmail({
44
48
  skipCssInline,
45
49
  test,
46
50
  }) {
47
- if (!config) {
48
- throw new Error('sendEmail: `config` missing')
49
- } else if (!config.baseUrl) {
50
- throw new Error('sendEmail: `config.baseUrl` is missing')
51
- } else if (!config.emailFrom) {
52
- throw new Error('sendEmail: `config.emailFrom` is missing')
53
- } else if (!test && (!config.mailgunKey || !config.mailgunDomain)) {
54
- throw new Error('sendEmail: `config.mailgunKey` or `config.mailgunDomain` is missing')
55
- } else if (!config.name) {
56
- throw new Error('sendEmail: `config.name` is missing')
57
- } else if (!template) {
58
- throw new Error('sendEmail: `template` missing')
59
- } else if (!to) {
60
- throw new Error('sendEmail: `to` missing')
51
+ const isTest = config.emailTestMode || test
52
+ if (!config) throw new Error('sendEmail: `config` missing')
53
+ for (const key of requiredEmailConfigKeys) {
54
+ if (!config[key]) throw new Error(`sendEmail: config.${key} is missing`)
55
+ }
56
+ if (!isTest && (!config.mailgunKey || !config.mailgunDomain)) {
57
+ throw new Error('sendEmail: config.mailgunKey and config.mailgunDomain are required')
61
58
  }
59
+ if (!template) throw new Error('sendEmail: `template` missing')
60
+ if (!to) throw new Error('sendEmail: `to` missing')
62
61
 
63
62
  // Setup nodemailer once
64
- if (!nodemailerMailgun && !test) {
63
+ if (!nodemailerMailgun && !isTest) {
65
64
  nodemailerMailgun = nodemailer.createTransport(
66
65
  mailgun({ auth: { api_key: config.mailgunKey, domain: config.mailgunDomain }})
67
66
  )
@@ -95,13 +94,13 @@ export async function sendEmail({
95
94
  bcc: bcc,
96
95
  emailTemplateDir: getDirectories(path, config.pwd).emailTemplateDir,
97
96
  from: from,
98
- isDev: config.baseUrl.match(/:/), // possibly use config.env here
97
+ isDev: config.env === 'development',
99
98
  recipientVariables: recipientVariables,
100
99
  replyTo: replyTo,
101
100
  skipCssInline: skipCssInline,
102
101
  subject: subject,
103
102
  template: template,
104
- test: config.emailTestMode || test,
103
+ test: isTest,
105
104
  to: to,
106
105
  url: config.baseUrl,
107
106
  }
@@ -187,24 +186,27 @@ function processTemplate(settings, html) {
187
186
  async function sendWithMailgun(settings, html) {
188
187
  // Supports batch sending via recipientVariables, limit 1000 emails
189
188
  // https://documentation.mailgun.com/en/latest/user_manual.html?highlight=batch%20sending#batch-sending
190
- let processedhtml = await processTemplate(settings, html)
191
- // console.log(settings)
189
+ const processedhtml = await processTemplate(settings, html)
190
+ const mailgunOpts = {
191
+ ...(settings.bcc && !settings.isDev? { bcc: settings.bcc } : {}),
192
+ from: settings.from,
193
+ html: processedhtml,
194
+ 'h:Reply-To': settings.replyTo,
195
+ subject: settings.subject,
196
+ to: settings.to,
197
+ ...(!settings.recipientVariables? {} : {
198
+ 'recipient-variables': typeof settings.recipientVariables == 'string'
199
+ ? settings.recipientVariables
200
+ : JSON.stringify(settings.recipientVariables),
201
+ }),
202
+ }
203
+ if (settings.test && settings.isDev) {
204
+ console.info('Test mode: sendEmail mailgunOpts', { ...mailgunOpts, html: null, 'recipient-variables': settings.recipientVariables })
205
+ }
192
206
  if (settings.test) return processedhtml
193
207
 
194
208
  return new Promise((resolve, reject) => {
195
- nodemailerMailgun.sendMail({
196
- ...(settings.bcc && !settings.isDev? { bcc: settings.bcc } : {}),
197
- from: settings.from,
198
- html: processedhtml,
199
- 'h:Reply-To': settings.replyTo,
200
- subject: settings.subject,
201
- to: settings.to,
202
- ...(!settings.recipientVariables? {} : {
203
- 'recipient-variables': typeof settings.recipientVariables == 'string'
204
- ? settings.recipientVariables
205
- : JSON.stringify(settings.recipientVariables),
206
- }),
207
- }, function(err, info) {
209
+ nodemailerMailgun.sendMail(mailgunOpts, function(err, info) {
208
210
  if (err) {
209
211
  console.error('SendEmail mailgun error')
210
212
  reject(err)
package/server/index.js CHANGED
@@ -24,7 +24,7 @@ export { userModel, companyModel, setupDefaultModels }
24
24
  export { setupRouter, middleware, isValidUserOrRespond, isAdminUser } from './router.js'
25
25
 
26
26
  // Export email utility
27
- export { sendEmail } from './email/index.js'
27
+ export { sendEmail, requiredEmailConfigKeys, optionalEmailConfigKeys } from './email/index.js'
28
28
 
29
29
  // Export API controllers
30
30
  export * from '../components/auth/auth.api.js'
@@ -21,6 +21,7 @@ export default {
21
21
  }],
22
22
  invites: [{
23
23
  email: { type: 'email', required: true },
24
+ firstName: { type: 'string', required: true },
24
25
  role: { type: 'string', enum: ['owner', 'manager'], required: true },
25
26
  inviteToken: { type: 'string', required: true },
26
27
  }],
@@ -57,8 +58,8 @@ export default {
57
58
  publicData: function(models) {
58
59
  return models
59
60
  },
60
- loginPopulate: function() {
61
- // return the company with expanded company.users
61
+ authPopulate: function() {
62
+ // Special method called by auth.api.js on authentication.
62
63
  return [
63
64
  {
64
65
  as: 'usersExpanded',
@@ -48,7 +48,8 @@ export default {
48
48
  ],
49
49
 
50
50
  methods: {
51
- loginPopulate: function() {
51
+ authPopulate: function() {
52
+ // Special method called by auth.api.js on authentication.
52
53
  return []
53
54
  },
54
55
  },
package/server/router.js CHANGED
@@ -387,7 +387,7 @@ export const middleware = {
387
387
  else next()
388
388
  },
389
389
  isUser: (req, res, next) => {
390
- if (!isValidUserOrRespond(req, res)) return
390
+ if (!isValidUserOrRespond(req, res)) return
391
391
  else next()
392
392
  },
393
393
  isParamUser: (req, res, next) => {
@@ -401,11 +401,11 @@ export const middleware = {
401
401
  else next()
402
402
  },
403
403
  isCompanyUser: (req, res, next) => {
404
- if (!isValidParamCompanyUserOrRespond(req)) return
404
+ if (!isValidParamCompanyUserOrRespond(req, res)) return
405
405
  else next()
406
406
  },
407
407
  isCompanyOwner: (req, res, next) => {
408
- if (!isValidParamCompanyUserOrRespond(req, true)) return
408
+ if (!isValidParamCompanyUserOrRespond(req, res, true)) return
409
409
  else next()
410
410
  },
411
411
  }
@@ -433,7 +433,8 @@ function isValidParamCompanyUserOrRespond(req, res, checkIsOwner = false) {
433
433
  const company = _company || req.user?.companies?.find((o) => o._id.toString() == req.params.cid)
434
434
  const isCompanyOwner = company?.users?.find((o) => o._id.toString() == req.user?._id?.toString() && o.role === 'owner')
435
435
  if (!isValidUserOrRespond(req, res)) return
436
- else if (!isAdminUser(req) && !company) res.unauthorized('You are not authorised to make this request.')
437
- else if (!isAdminUser(req) && checkIsOwner && !isCompanyOwner) res.unauthorized('Only owners can make this request.')
436
+ else if (!isAdminUser(req) && configLocal.isNotMultiTenant) res.unauthorized('Only admins can make this request.')
437
+ else if (!isAdminUser(req) && !company) res.unauthorized('Only company users can make this request.')
438
+ else if (!isAdminUser(req) && checkIsOwner && !isCompanyOwner) res.unauthorized('Only company owners can make this request.')
438
439
  else return true
439
440
  }
@@ -1,7 +1,11 @@
1
1
  export function resetInstructions(req: any, res: any): Promise<void>;
2
- export function inviteInstructions(req: any, res: any): Promise<void>;
2
+ export function inviteInstructions(req: any, res: any): Promise<any>;
3
+ export function resendInstructions(req: any, res: any): Promise<any>;
3
4
  export function resetConfirm(req: any, res: any): Promise<void>;
4
5
  export function inviteConfirm(req: any, res: any): Promise<void>;
6
+ export function invitePreConfirm(req: any, res: any): Promise<void>;
7
+ export function updateMemberRole(req: any, res: any): Promise<void>;
8
+ export function removeMember(req: any, res: any): Promise<void>;
5
9
  export function userFindFromProvider(query: any, passwordToCheck: any, ...args: any[]): Promise<any>;
6
10
  export function userSigninGetStore(user: any, isDesktop: any): Promise<{
7
11
  jwt: string;
@@ -24,10 +28,11 @@ export function userCreate({ password, password2, company, ...userDataProp }: {
24
28
  password?: string;
25
29
  password2?: string;
26
30
  company?: string;
27
- }, baseUrl?: string, skipSendEmail?: boolean): Promise<object>;
31
+ }, baseUrl?: string, invite: any, skipSendEmail?: boolean): Promise<object>;
28
32
  export function passwordValidate(password: string, password2: any): Promise<void>;
29
33
  export function tokenCreate(modelName: any, id: any): Promise<any>;
30
34
  export function tokenParse(token: any, modelName: any, maxAgeMs?: number): any;
35
+ export function addUserToCompany(companyId: any, userId: any, role: any, token: any, justValidate: any): Promise<any>;
31
36
  export function tokenConfirmForReset(req: any): Promise<{
32
37
  jwt: string;
33
38
  user: any;
@@ -39,24 +44,36 @@ export function tokenConfirmForSingleTenant(req: any, isReset: any): Promise<{
39
44
  export function tokenConfirmForMultiTenant(req: any): Promise<{
40
45
  jwt: string;
41
46
  user: any;
47
+ } | {
48
+ isExistingUser: boolean;
49
+ email: any;
50
+ } | {
51
+ isExistingUser?: undefined;
52
+ email?: undefined;
42
53
  }>;
43
54
  /**
44
55
  * Creates and sends a reset or invite token to a user or company
45
56
  * @param {object} options
46
- * @param {'reset' | 'invite' | 'companyInvite'} options.type - token type (default: 'reset')
47
- * @param {string} options._id - user or company id
48
- * @param {string} options.email - recipient email
49
- * @param {string} options.firstName - recipient first name
57
+ * @param {'reset' | 'invite' | 'companyInvite'} options.type - token type
58
+ * @param {string} options.id - user or company id
59
+ * @param {{
60
+ * email: string,
61
+ * firstName: string,
62
+ * [key: string]: any, // other fields to include in the invite row
63
+ * }} options.payload
50
64
  * @param {function} [options.beforeUpdate] - runs before updating the model with the token, return null to skip update
51
65
  * @param {function} [options.beforeSendEmail] - runs before sending the email, receives (options, token)
52
66
  * @param {string} [options.baseUrl] - baseUrl to use for the email
53
67
  * @returns {Promise<{token: string, mailgunPromise: Promise<unknown>}>}
54
68
  */
55
- export function tokenSend({ type, _id, email, firstName, beforeUpdate, beforeSendEmail, baseUrl }: {
69
+ export function tokenSend({ type, id, payload, beforeUpdate, beforeSendEmail, baseUrl, isResend }: {
56
70
  type: "reset" | "invite" | "companyInvite";
57
- _id: string;
58
- email: string;
59
- firstName: string;
71
+ id: string;
72
+ payload: {
73
+ email: string;
74
+ firstName: string;
75
+ [key: string]: any;
76
+ };
60
77
  beforeUpdate?: Function;
61
78
  beforeSendEmail?: Function;
62
79
  baseUrl?: string;
@@ -64,11 +81,14 @@ export function tokenSend({ type, _id, email, firstName, beforeUpdate, beforeSen
64
81
  token: string;
65
82
  mailgunPromise: Promise<unknown>;
66
83
  }>;
84
+ export function getBaseUrl(req: any): any;
67
85
  export function resolveBaseUrl(reqUrl: any, cfgUrl: any): any;
86
+ export function ensureNotLastOwner(companyUsers: any, idNowNonOwner: any): void;
68
87
  export namespace auth {
69
88
  export { userFindFromProvider };
70
89
  export { userSigninGetStore };
71
90
  export { getStore };
91
+ export { addUserToCompany };
72
92
  export { userCreate };
73
93
  export { passwordValidate };
74
94
  export { tokenCreate };
@@ -77,6 +97,11 @@ export namespace auth {
77
97
  export { tokenConfirmForReset };
78
98
  export { tokenConfirmForSingleTenant };
79
99
  export { tokenConfirmForMultiTenant };
100
+ export { getBaseUrl };
101
+ export { invitePreConfirm };
102
+ export { inviteConfirm };
103
+ export { updateMemberRole };
104
+ export { removeMember };
80
105
  }
81
106
  export const routes: {
82
107
  'get /api/store': (typeof store)[];
@@ -86,8 +111,12 @@ export const routes: {
86
111
  'post /api/reset-instructions': (typeof resetInstructions)[];
87
112
  'post /api/reset-confirm': (typeof resetConfirm)[];
88
113
  'post /api/invite-instructions': (typeof inviteInstructions)[];
89
- 'post /api/invite-confirm': (typeof inviteConfirm)[];
114
+ 'post /api/resend-instructions': (typeof resendInstructions)[];
115
+ 'get /api/invite-pre-confirm/:token': (typeof invitePreConfirm)[];
116
+ 'post /api/invite-confirm/:token': (typeof inviteConfirm)[];
90
117
  'delete /api/account/:uid': (typeof remove)[];
118
+ 'put /api/company/:cid/member-role/:uidOrEmail': (string | typeof updateMemberRole)[];
119
+ 'delete /api/company/:cid/member/:uidOrEmail': (string | typeof removeMember)[];
91
120
  setup: typeof setup;
92
121
  };
93
122
  declare function store(req: any, res: any): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.api.d.ts","sourceRoot":"","sources":["../../../components/auth/auth.api.js"],"names":[],"mappings":"AA2KA,qEAeC;AAED,sEAuCC;AAED,gEAMC;AAED,iEAgBC;AAID,qGAkDC;AAED;;;GAOC;AAED;;GAKC;AAID;;;;;;;;;GASG;AACH,8EAPG;IAA0B,QAAQ,GAA1B,MAAM;IACY,SAAS,GAA3B,MAAM;IACY,OAAO,GAAzB,MAAM;CACd,YAAQ,MAAM,kBACN,OAAO,GACL,OAAO,CAAC,MAAM,CAAC,CA0D3B;AAED,kFAiBC;AAED,mEAOC;AAED,+EAWC;AAED;;;GAEC;AAED;;;GAuBC;AAED;;;GAgCC;AAED;;;;;;;;;;;GAWG;AACH,mGATG;IAAsD,IAAI,EAAlD,OAAO,GAAG,QAAQ,GAAG,eAAe;IACpB,GAAG,EAAnB,MAAM;IACU,KAAK,EAArB,MAAM;IACU,SAAS,EAAzB,MAAM;IACa,YAAY;IACZ,eAAe;IACjB,OAAO,GAAxB,MAAM;CACd,GAAU,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAC,CAAC,CAuCtE;AAMD,8DAmBC;;;;;;;;;;;;;;AAviBD;;;;;;;;;;;EAaC;AAwED,0DAEC;AAgCD,mDAEC;AAtBD,iDAkBC;AA5BD,2DAQC;AA0BD,2DAwBC;AApID,0EAoEC"}
1
+ {"version":3,"file":"auth.api.d.ts","sourceRoot":"","sources":["../../../components/auth/auth.api.js"],"names":[],"mappings":"AAoLA,qEAUC;AAED,qEAwEC;AAED,qEAGC;AAED,gEAMC;AAED,iEAgBC;AAED,oEAGC;AAED,oEA8BC;AAED,gEAgCC;AAID,qGAkDC;AAED;;;GAOC;AAED;;GAKC;AAID;;;;;;;;;GASG;AACH,8EAPG;IAA0B,QAAQ,GAA1B,MAAM;IACY,SAAS,GAA3B,MAAM;IACY,OAAO,GAAzB,MAAM;CACd,YAAQ,MAAM,+BACN,OAAO,GACL,OAAO,CAAC,MAAM,CAAC,CAqE3B;AAED,kFAiBC;AAED,mEAOC;AAED,+EAWC;AAED,sHAiBC;AAED;;;GAEC;AAED;;;GA0BC;AAED;;;;;;;;;GAmCC;AAED;;;;;;;;;;;;;;GAcG;AACH,mGAZG;IAAsD,IAAI,EAAlD,OAAO,GAAG,QAAQ,GAAG,eAAe;IACpB,EAAE,EAAlB,MAAM;IAKH,OAAO,EAJV;QACN,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QACtB,CAAK,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB;IAC0B,YAAY;IACZ,eAAe;IACjB,OAAO,GAAxB,MAAM;CACd,GAAU,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAC,CAAC,CAmEtE;AAED,0CAEC;AAED,8DAmBC;AAED,gFAKC;;;;;;;;;;;;;;;;;;;;AA/tBD;;;;;;;;;;;;;;;EAiBC;AA0ED,0DAEC;AAgCD,mDAEC;AAtBD,iDAkBC;AA5BD,2DAQC;AA0BD,2DAwBC;AAtID,0EAsEC"}
@@ -29,6 +29,8 @@ export function sendEmail({ template, to, config, bcc, data, from, replyTo, reci
29
29
  skipCssInline?: boolean;
30
30
  test?: boolean;
31
31
  }): Promise<[any, any]>;
32
+ export const requiredEmailConfigKeys: string[];
33
+ export const optionalEmailConfigKeys: string[];
32
34
  /**
33
35
  * Sends an email using a predefined template, with optional data/or recipientVariables
34
36
  */
@@ -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,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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../server/email/index.js"],"names":[],"mappings":"AAmBA;;;;;;;;;;;;;;;;;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,CA4E/B;AAhGD,+CAA8E;AAC9E,+CAAuG;;;;qBAK1F;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"}
@@ -2,7 +2,6 @@ export * from "../util.js";
2
2
  export * from "../components/auth/auth.api.js";
3
3
  export * from "../components/billing/stripe.api.js";
4
4
  export * as util from "../util.js";
5
- export { sendEmail } from "./email/index.js";
6
5
  export { routes as authRoutes } from "../components/auth/auth.api.js";
7
6
  export { routes as stripeRoutes } from "../components/billing/stripe.api.js";
8
7
  /**
@@ -23,4 +22,5 @@ export function setupDefaultModels(db: any): Promise<void>;
23
22
  export { userModel, companyModel };
24
23
  export { currencies, countries } from "./constants.js";
25
24
  export { setupRouter, middleware, isValidUserOrRespond, isAdminUser } from "./router.js";
25
+ export { sendEmail, requiredEmailConfigKeys, optionalEmailConfigKeys } from "./email/index.js";
26
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../server/index.js"],"names":[],"mappings":";;;;;;;;;;+BAOa,OAAO,aAAa,EAAE,gBAAgB;;;;sBACtC,OAAO,gBAAgB,EAAE,OAAO;;;;uBAChC,OAAO,gBAAgB,EAAE,QAAQ;sBAIxB,kBAAkB;yBACf,qBAAqB;AAC9C,2DAIC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../server/index.js"],"names":[],"mappings":";;;;;;;;;+BAOa,OAAO,aAAa,EAAE,gBAAgB;;;;sBACtC,OAAO,gBAAgB,EAAE,OAAO;;;;uBAChC,OAAO,gBAAgB,EAAE,QAAQ;sBAIxB,kBAAkB;yBACf,qBAAqB;AAC9C,2DAIC"}
@@ -112,6 +112,10 @@ declare namespace _default {
112
112
  type: string;
113
113
  required: boolean;
114
114
  };
115
+ firstName: {
116
+ type: string;
117
+ required: boolean;
118
+ };
115
119
  role: {
116
120
  type: string;
117
121
  enum: string[];
@@ -128,7 +132,7 @@ declare namespace _default {
128
132
  let afterFind: ((data: any) => Promise<void>)[];
129
133
  namespace methods {
130
134
  function publicData(models: any): any;
131
- function loginPopulate(): {
135
+ function authPopulate(): {
132
136
  as: string;
133
137
  from: string;
134
138
  let: {