nitro-web 0.0.165 → 0.0.167

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
@@ -219,14 +219,10 @@ function getRouter({ settings, config }: { settings: Settings, config: Config })
219
219
  }
220
220
  for (const key of route.middleware) {
221
221
  const error = settings.middleware[key](route, exposedStoreData || {})
222
+ // Note: the redirect uses the new pathname for query string values, e.g. '?example=value'. We also can't use the
223
+ // current pathname, as this doesn't exist on page refresh.
222
224
  if (error && error.redirect) {
223
- // Redirect() will use the new pathname instead of the current one for values without a pathname causing guard
224
- // redirect loops! We assume these query string redirects are intended to be relative to the current path.
225
- if (error.redirect.startsWith('?')) {
226
- return redirect(window.location.pathname + error.redirect)
227
- } else {
228
- return redirect(error.redirect)
229
- }
225
+ return redirect(error.redirect)
230
226
  }
231
227
  }
232
228
  return null
@@ -286,7 +282,7 @@ function catchRedirectLoop(request: Request, routeName: string) {
286
282
  lastRedirectTimesForSameUrl.push(Date.now())
287
283
  if (lastRedirectTimesForSameUrl.length > 5 && (Date.now() < lastRedirectTime + 100)) {
288
284
  throw new Error(
289
- `Nitro: A redirect loop has been detected for route '${routeName}'. This ismost likely due to a redirect loop caused by your middleware.`
285
+ `Nitro: A redirect loop has been detected for route '${routeName}'. This is most likely due to a redirect loop caused by your middleware. Consider triggered by redirect values without a pathname, e.g. '?example=value'.`
290
286
  )
291
287
  }
292
288
  } else {
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
- // E.g. Cookie matching handy for rare issues, e.g. signout > signin (to a different user on another tab)
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.authid = newStore?.user?._id
44
+ axios().defaults.headers.requestingUserId = newStore?.user?._id
45
45
  }
46
46
  return newStore
47
47
  }
@@ -1,7 +1,11 @@
1
1
  export function NotFound() {
2
2
  return (
3
- <div class="pt-14 text-center" style={{'minHeight': '300px'}}>
4
- Sorry, nothing found.
3
+ <div style={{'minHeight': '300px'}}>
4
+ <span class="h1">Page Not Found</span><br />
5
+ <br />
6
+ The page you&apos;re looking for doesn&apos;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.165",
3
+ "version": "0.0.167",
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/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
- // Still need to remove cookie matching in favour of uid..
378
- // E.g. Cookie matching handy for rare issues, e.g. signout > signin (to a different user on another tab)
379
- const user = req.user
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
- // todo: need to double check that uid is always defined
406
- let uid = req.params.uid
407
- if (req.user && (typeof uid == 'undefined' || req.user._id?.toString() == uid)) next()
408
- else if (req.user) res.unauthorized('You are not authorised to make this request.')
409
- else res.unauthorized('Please sign in first.')
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
  }
@@ -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
@@ -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;AAgKD,+BAA+B;AAC/B,yBADW,gBAAgB,CA8F1B;sBA/YY,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"}
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"}