Pārlūkot izejas kodu

Authorization in progress

Alan Colon 7 gadi atpakaļ
vecāks
revīzija
226b36e50c

+ 6 - 0
app/api-service.js

@@ -13,6 +13,7 @@ app.service('api', function($http) {
 
   this.login = data => this.post('/api/auth/login', data)
   this.setToken = token => {
+    localStorage.setItem('token', token)
     this.token = token
     this.opts.headers.authentication = `Bearer ${token}`
   }
@@ -28,4 +29,9 @@ app.service('api', function($http) {
     autocomplete: (searchText) => this.get(apiPrefix, { params: { q: searchText } }),
     lookup: (ids) => this.get(apiPrefix, { params: { ids: ids.join(',')}})
   })
+
+  if (localStorage.getItem('token')) {
+    this.setToken(localStorage.getItem('token'))
+  }
+
 })

+ 2 - 2
app/components/user-area.js

@@ -38,12 +38,12 @@ app.component('appUserArea', {
             Users
           </md-button>
         </md-menu-item>
-        <md-menu-item>
+        <!-- <md-menu-item>
           <md-button ng-href="/roles">
             <md-icon md-svg-icon="${roleIcon}"></md-icon>
             Roles
           </md-button>
-        </md-menu-item>
+        </md-menu-item> -->
       </md-sidenav>
       <md-content flex>
         <md-toolbar>

+ 20 - 0
lib/controllers/auth/decode.js

@@ -0,0 +1,20 @@
+const JWT = require('jsonwebtoken')
+const config = require('../../../config')
+const decode = async (req, res, next) => {
+  const r = /^Bearer (.*)$/.exec(req.headers['authentication'])
+  if (r) {
+    const token = r[1]
+    try {
+      const decoded = await JWT.verify(token, config.auth.jwtSecret)
+      req.user = decoded.user
+      next()
+    } catch (err) {
+      res.setHeader('X-JWT-Error', err.message || err.toString())
+      next()
+    }
+  } else {
+    next()
+  }
+}
+
+module.exports = decode

+ 4 - 1
lib/controllers/auth/index.js

@@ -1,3 +1,6 @@
 module.exports = {
-  login: require('./login')
+  login: require('./login'),
+  decode: require('./decode'),
+  verify: require('./verify'),
+  permissions: require('./permissions')
 }

+ 6 - 2
lib/controllers/auth/login.js

@@ -16,9 +16,13 @@ module.exports = {
         await Session.create({
           id: sid,
           startAt: Date.now(),
-          endAt : exp
+          endAt : exp,
         })
-        const token = JWT.sign({sid, exp}, config.auth.jwtSecret);
+        const token = JWT.sign({
+          sid,
+          exp, 
+          user: user.sanitize()
+        }, config.auth.jwtSecret);
         return res.status(200).send({
           user: user.sanitize(),
           token

+ 11 - 0
lib/controllers/auth/verify.js

@@ -0,0 +1,11 @@
+const verify = (permission) => (req, res, next) => {
+  const verified = !!req.user
+  if (!verified) {
+    if (res) res.status(403).end()
+  } else {
+    if (next) next()
+  }
+  return verified
+}
+
+module.exports = verify

+ 19 - 6
lib/crud/defaults.js

@@ -1,5 +1,5 @@
-const { pascal, title, camel, param } = require('change-case')
-const plural = require('plural')
+const { pascal, title, camel, param, constant } = require('change-case')
+const pluralLib = require('plural')
 
 /** @define CrudOptions
  * @property {string} titleName Type name in Title Case. This is used for labels like "New {Title Name}"
@@ -10,6 +10,8 @@ const plural = require('plural')
  * @property {string} camelPlural Plural type name in camelCase.
  * @property {string} paramName Type name in param-case.
  * @property {string} paramPlural Plural type name in param-case. This is used for urls, like "/api/{param-plural}"
+ * @property {string} constantName Type name in CONSTANT_CASE. This is used for permissions like CONSTANT_NAME_READ.
+ * @property {string} constantPlural Plural type name in CONSTANT_CASE.
  * @property {string} apiPrefix API url path prefix. Default: /api/{param-plural}
  * @property {string} appPrefix APP url path prefix. Default: /{param-plural}
  * @property {CrudColumnOptions[]} columns
@@ -24,24 +26,35 @@ const plural = require('plural')
  * @property {boolean} inList Default: true. Includes column in list page.
  */
 
+const plural = text => {
+  words = title(text).split(' ')
+  words[words.length - 1] = pluralLib(words[words.length - 1].toLowerCase())
+  return words.join(' ')  
+}
 
 const defaults = (opts) => {
   opts = Object.assign({}, opts)
   if (!opts.pascalName) opts.pascalName = pascal(opts.titleName || opts.camelName || opts.paramName || (opts.Type && opts.Type.name) || '')
   if (!opts.pascalName) throw new Error('pascalName is required')
   if (opts.pascalName !== pascal(opts.pascalName)) throw new Error('pascalName should be PascalCased')
-  if (!opts.pascalPlural) opts.pascalPlural = plural(opts.pascalName)
+  if (!opts.pascalPlural) opts.pascalPlural = pascal(plural(opts.pascalName))
   if (opts.pascalPlural !== pascal(opts.pascalPlural)) throw new Error('pascalPlural should be PascalCased')
   if (!opts.titleName) opts.titleName = title(opts.pascalName)
-  if (!opts.titlePlural) opts.titlePlural = plural(opts.titleName)
+  if (!opts.titlePlural) opts.titlePlural = title(plural(opts.titleName))
   if (!opts.camelName) opts.camelName = camel(opts.pascalName)
   if (opts.camelName !== camel(opts.camelName)) throw new Error('camelName should be camelCased')
-  if (!opts.camelPlural) opts.camelPlural = plural(opts.camelName)
+  if (!opts.camelPlural) opts.camelPlural = camel(plural(opts.camelName))
   if (opts.camelPlural !== camel(opts.camelPlural)) throw new Error('camelPlural should be camelCased')
   if (!opts.paramName) opts.paramName = param(opts.pascalName)
   if (opts.paramName !== param(opts.paramName)) throw new Error('paramName should be param-cased')
-  if (!opts.paramPlural) opts.paramPlural = plural(opts.paramName)
+  if (!opts.paramPlural) opts.paramPlural = param(plural(opts.paramName))
   if (opts.paramPlural !== param(opts.paramPlural)) throw new Error('paramPlural should be param-cased')
+
+  if (!opts.constantName) opts.constantName = constant(opts.pascalName)
+  if (opts.constantName !== constant(opts.constantName)) throw new Error('constantName should be constant-cased')
+  if (!opts.constantPlural) opts.constantPlural = constant(plural(opts.constantName))
+  if (opts.constantPlural !== constant(opts.constantPlural)) throw new Error('constantPlural should be constant-cased')
+
   if (!opts.apiPrefix) opts.apiPrefix = `/api/${opts.paramPlural}`
   if (!opts.appPrefix) opts.appPrefix = `/${opts.paramPlural}`
   if (!opts.routeParam) opts.routeParam = `${opts.camelName}Id`

+ 20 - 7
lib/crud/routes.js

@@ -1,15 +1,28 @@
 const defaults = require('./defaults')
 const asyncHandler = require('express-async-handler')
+const {verify, permissions} = require('../controllers/auth')
 
 module.exports = (opts) => {
   opts = defaults(opts)
   const { app, controller } = opts
-  if (controller.list) app.get(`/api/${opts.paramPlural}`, asyncHandler(controller.list))
-  if (controller.create) app.post(`/api/${opts.paramPlural}`, asyncHandler(controller.create))
-  if (controller.trash) app.get(`/api/${opts.paramPlural}/trash`, asyncHandler(controller.trash))
-  if (controller.read) app.get(`/api/${opts.paramPlural}/:${opts.routeParam}`, asyncHandler(controller.read))
-  if (controller.update) app.patch(`/api/${opts.paramPlural}/:${opts.routeParam}`, asyncHandler(controller.update))
-  if (controller.delete) app.delete(`/api/${opts.paramPlural}/:${opts.routeParam}`, asyncHandler(controller.delete))
-  if (controller.undelete) app.delete(`/api/${opts.paramPlural}/trash/:${opts.routeParam}`, asyncHandler(controller.undelete))
+  const TYPE_CREATE = `${opts.constantName}_CREATE`
+  const TYPE_READ = `${opts.constantName}_READ`
+  const TYPE_UPDATE = `${opts.constantName}_UPDATE`
+  const TYPE_DELETE = `${opts.constantName}_DELETE`
+  const TYPE_UNDELETE = `${opts.constantName}_UNDELETE`
+
+  permissions.register(TYPE_CREATE)
+  permissions.register(TYPE_READ)
+  permissions.register(TYPE_UPDATE)
+  permissions.register(TYPE_DELETE)
+  permissions.register(TYPE_UNDELETE)
+
+  if (controller.list) app.get(`/api/${opts.paramPlural}`, verify(TYPE_READ), asyncHandler(controller.list))
+  if (controller.create) app.post(`/api/${opts.paramPlural}`, verify(TYPE_CREATE), asyncHandler(controller.create))
+  if (controller.trash) app.get(`/api/${opts.paramPlural}/trash`, verify(TYPE_UNDELETE), asyncHandler(controller.trash))
+  if (controller.read) app.get(`/api/${opts.paramPlural}/:${opts.routeParam}`, verify(TYPE_READ), asyncHandler(controller.read))
+  if (controller.update) app.patch(`/api/${opts.paramPlural}/:${opts.routeParam}`, verify(TYPE_UPDATE), asyncHandler(controller.update))
+  if (controller.delete) app.delete(`/api/${opts.paramPlural}/:${opts.routeParam}`, verify(TYPE_DELETE), asyncHandler(controller.delete))
+  if (controller.undelete) app.delete(`/api/${opts.paramPlural}/trash/:${opts.routeParam}`, verify(TYPE_UNDELETE), asyncHandler(controller.undelete))
   console.log(`crud: /api/${opts.paramPlural}`)
 }

+ 1 - 0
lib/routes.js

@@ -4,6 +4,7 @@ const C = require('./controllers')
 
 module.exports = app => {
   app.post('/api/auth/login', asyncHandler(C.auth.login.post))
+  app.get('/api/auth/permissions', asyncHandler(C.auth.permissions.list))
   crudRoutes({ app, controller: C.user, pascalName: 'User' })
   crudRoutes({ app, controller: C.role, pascalName: 'Role' })
 }

+ 1 - 0
lib/server.js

@@ -9,6 +9,7 @@ const routes = require('./routes')
 const serverFactory = require('../auto-crud/server-factory')
 
 const app = express()
+app.use(C.auth.decode)
 app.use(bodyParser.json())
 
 routes(app)