├── .editorconfig ├── .env.example ├── .gitignore ├── .npmrc ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json ├── public └── index.html ├── server ├── config.js ├── data.js ├── faker.js ├── index.js ├── token.js └── user.js └── vercel.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # For more information about the properties used in 2 | # this file, please see the EditorConfig documentation: 3 | # https://editorconfig.org/ 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 2 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [COMMIT_EDITMSG] 19 | max_line_length = 0 20 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | JWT_SECRET= 2 | JWT_ISSUER= 3 | JWT_AUDIENCE= 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vercel 3 | .env 4 | package-lock.json 5 | yarn.lock 6 | npm-debug.log 7 | yarn-error.log 8 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry = https://registry.npm.taobao.org/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - lts/* 7 | 8 | script: 9 | - npm run lint 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 zce (https://zce.me) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dashboard-server 2 | 3 | [![Build Status][travis-img]][travis-url] 4 | [![Dependency Status][dependency-img]][dependency-url] 5 | [![devDependency Status][devdependency-img]][devdependency-url] 6 | [![Code Style][style-img]][style-url] 7 | 8 | > A JSON file RESTful API with authorization based on [json-server](https://github.com/typicode/json-server) for [zce/dashboard](https://github.com/zce/dashboard) 9 | 10 | ## Usage 11 | 12 | ```sh 13 | # clone repo 14 | $ git clone https://github.com/zce/dashboard-server.git 15 | 16 | # change directory 17 | $ cd 18 | 19 | # install dependencies 20 | $ yarn # or npm install 21 | 22 | # serve with nodemon at http://localhost:3000 23 | $ yarn dev 24 | ``` 25 | 26 | ## JWT Authorization Endpoints 27 | 28 | > with [jsonwebtoken](http://jwt.io) 29 | 30 | ### POST `/tokens` 31 | 32 | create token 33 | 34 | ```sh 35 | # Content-type: x-www-form-urlencoded 36 | $ curl -X POST -d "username=zce&password=wanglei" http://localhost:3000/tokens 37 | # Content-type: application/json 38 | $ curl -X POST -H "Content-type: application/json" -d "{\"username\":\"zce\",\"password\":\"wanglei\"}" http://localhost:3000/tokens 39 | ``` 40 | 41 | request body 42 | 43 | ```js 44 | { username: 'zce', password: 'wanglei' } 45 | ``` 46 | 47 | ### GET `/tokens` 48 | 49 | check token 50 | 51 | ```sh 52 | $ curl -H "Authorization: Bearer " http://localhost:3000/tokens 53 | ``` 54 | 55 | request headers 56 | 57 | ```js 58 | { 59 | headers: { Authorization: 'Bearer ' } 60 | } 61 | ``` 62 | 63 | ### DELETE `/tokens` 64 | 65 | revoke token 66 | 67 | ```sh 68 | $ curl -X DELETE -H "Authorization: Bearer " http://localhost:3000/tokens 69 | ``` 70 | 71 | request headers 72 | 73 | ```js 74 | { 75 | headers: { Authorization: 'Bearer ' } 76 | } 77 | ``` 78 | 79 | ## JSON Server Resources Endpoints 80 | 81 | - Comments: `/comments/:id?` 82 | - Posts: `/posts/:id?` 83 | - Terms: `/terms/:id?` 84 | - Users: `/users/:id?` 85 | - Options: `/options/:id?` 86 | 87 | To access and modify resources, you can use any HTTP method: `GET` `POST` `PUT` `PATCH` `DELETE` `OPTIONS` 88 | 89 | ## Additional Endpoints 90 | 91 | ### GET `/users/me` 92 | 93 | get current login user information 94 | 95 | ```sh 96 | $ curl -H "Authorization: Bearer " http://localhost:3000/users/me 97 | ``` 98 | 99 | request headers 100 | 101 | ```js 102 | { 103 | headers: { Authorization: 'Bearer ' } 104 | } 105 | ``` 106 | 107 | ## Backdoor Endpoints 108 | 109 | ### GET `/backdoor/reset` 110 | 111 | reset the database to its initial state 112 | 113 | ```sh 114 | $ curl http://localhost:3000/backdoor/reset 115 | ``` 116 | 117 | ### GET `/backdoor/delay` 118 | 119 | add a delay of 1000ms for each endpoint 120 | 121 | ```sh 122 | $ curl http://localhost:3000/backdoor/delay 123 | ``` 124 | 125 | ## Related 126 | 127 | - [zce/locally-server](https://github.com/zce/locally-server) - A JSON file RESTful API without JWT authorization 128 | 129 | ## License 130 | 131 | [MIT](LICENSE) © [汪磊](https://zce.me) 132 | 133 | 134 | 135 | [travis-img]: https://img.shields.io/travis/com/zce/dashboard-server.svg 136 | [travis-url]: https://travis-ci.com/zce/dashboard-server 137 | [dependency-img]: https://img.shields.io/david/zce/dashboard-server.svg 138 | [dependency-url]: https://david-dm.org/zce/dashboard-server 139 | [devdependency-img]: https://img.shields.io/david/dev/zce/dashboard-server.svg 140 | [devdependency-url]: https://david-dm.org/zce/dashboard-server?type=dev 141 | [style-img]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg 142 | [style-url]: https://standardjs.com/ 143 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const server = require('./server') 2 | 3 | server.listen(3000, () => { 4 | console.log('JSON Server is running: http://localhost:3000') 5 | }) 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashboard-server", 3 | "version": "0.2.0", 4 | "description": "A JSON file RESTful API with authorization based on json-server for zce/dashboard", 5 | "keywords": [ 6 | "rest api", 7 | "rest", 8 | "api", 9 | "zce" 10 | ], 11 | "homepage": "https://github.com/zce/dashboard-server#readme", 12 | "bugs": { 13 | "url": "https://github.com/zce/dashboard-server/issues" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/zce/dashboard-server.git" 18 | }, 19 | "license": "MIT", 20 | "author": { 21 | "name": "zce", 22 | "email": "w@zce.me", 23 | "url": "https://zce.me" 24 | }, 25 | "main": "server/index.js", 26 | "scripts": { 27 | "lint": "standard", 28 | "dev": "nodemon" 29 | }, 30 | "renovate": { 31 | "extends": [ 32 | "zce" 33 | ] 34 | }, 35 | "dependencies": { 36 | "bson-objectid": "^1.3.1", 37 | "dotenv": "^8.2.0", 38 | "express-jwt": "^6.0.0", 39 | "faker": "^4.1.0", 40 | "json-server": "^0.16.1", 41 | "jsonwebtoken": "^8.5.1", 42 | "uuid": "^8.3.0" 43 | }, 44 | "devDependencies": { 45 | "nodemon": "2.0.4", 46 | "standard": "14.3.4" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Server « Dashboard 7 | 8 | 9 | 10 | 25 | 26 | 27 | 28 | 33 | 34 |
35 |
36 |

Introduction

37 |

38 | This is a JSON file RESTful API with authorization based on json-server for zce/dashboard 39 |
ε=ε=ε=(~ ̄▽ ̄)~ 40 |

41 |

Getting started

42 |
43 | 44 |
45 |

JWT Authorization Endpoints

46 |
47 |

working with jsonwebtoken

48 |
49 |

POST /tokens

50 |

create token

51 |
# Content-type: x-www-form-urlencoded
 52 | $ curl -X POST -d "username=zce&password=wanglei" http://localhost:3000/tokens
 53 | # Content-type: application/json
 54 | $ curl -X POST -H "Content-type: application/json" -d "{\"username\":\"zce\",\"password\":\"wanglei\"}" http://localhost:3000/tokens
55 |

request body

56 |
{ username: 'zce', password: 'wanglei' }
57 |

GET /tokens

58 |

check token

59 |
$ curl -H "Authorization: Bearer <jwt-string>" http://localhost:3000/tokens
60 |

request headers

61 |
{
 62 |   headers: { Authorization: 'Bearer <jwt-string>' }
 63 | }
64 |

DELETE /tokens

65 |

revoke token

66 |
$ curl -X DELETE -H "Authorization: Bearer <jwt-string>" http://localhost:3000/tokens
67 |

request headers

68 |
{
 69 |   headers: { Authorization: 'Bearer <jwt-string>' }
 70 | }
71 |
72 | 73 |
74 |

JSON Server Resources Endpoints

75 |
    76 |
  • Comments: /comments/:id?
  • 77 |
  • Posts: /posts/:id?
  • 78 |
  • Terms: /terms/:id?
  • 79 |
  • Users: /users/:id?
  • 80 |
  • Options: /options/:id?
  • 81 |
82 |

83 | To access and modify resources, you can use any HTTP method:
84 | GET 85 | POST 86 | PUT 87 | PATCH 88 | DELETE 89 | OPTIONS 90 |

91 |
92 | 93 |
94 |

Additional Endpoints

95 |

GET /users/me

96 |

get current login user information

97 |
$ curl -H "Authorization: Bearer <jwt-string>" http://localhost:3000/users/me
98 |

request headers

99 |
{
100 |   headers: { Authorization: 'Bearer <jwt-string>' }
101 | }
102 |
103 | 104 |
105 |

Backdoor Endpoints

106 |

GET /backdoor/reset

107 |

reset the database to its initial state

108 |
$ curl http://localhost:3000/backdoor/reset
109 |

GET /backdoor/delay

110 |

add a delay of 1000ms for each endpoint

111 |
$ curl http://localhost:3000/backdoor/delay
112 |
113 |
114 | 115 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /server/config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | 3 | module.exports = { 4 | secret: process.env.JWT_SECRET, 5 | issuer: process.env.JWT_ISSUER, 6 | audience: process.env.JWT_AUDIENCE, 7 | expires: 24 * 60 * 60, // 24h 8 | // enable delay for testing 9 | enableDelay: false 10 | } 11 | -------------------------------------------------------------------------------- /server/data.js: -------------------------------------------------------------------------------- 1 | const os = require('os') 2 | const fs = require('fs') 3 | const path = require('path') 4 | 5 | const faker = require('./faker') 6 | 7 | // TODO: Revoked tokens store 8 | const revokedTokens = [] 9 | 10 | const dbFilename = path.join(os.tmpdir(), 'db.json') 11 | 12 | const init = () => { 13 | const data = faker() 14 | fs.writeFileSync(dbFilename, JSON.stringify(data)) 15 | return dbFilename 16 | } 17 | 18 | const getDatabase = () => { 19 | const content = fs.readFileSync(dbFilename, 'utf8') 20 | return JSON.parse(content) 21 | } 22 | 23 | const getUsers = () => { 24 | const db = getDatabase() 25 | db.users = db.users || [] 26 | return db.users.concat(faker.fallbackUsers) 27 | } 28 | 29 | const getUserBySlug = slug => { 30 | const users = getUsers() 31 | return users.find(u => u.slug === slug) 32 | } 33 | 34 | const getUserByUsername = username => { 35 | const users = getUsers() 36 | return users.find(u => u.username.toLowerCase() === username.toLowerCase()) 37 | } 38 | 39 | const revokeToken = payload => { 40 | payload && payload.uuid && revokedTokens.push(payload.uuid) 41 | } 42 | 43 | const isRevokedToken = payload => { 44 | payload && payload.uuid && revokedTokens.includes(payload.uuid) 45 | } 46 | 47 | module.exports = { init, getDatabase, getUserBySlug, getUserByUsername, revokeToken, isRevokedToken } 48 | -------------------------------------------------------------------------------- /server/faker.js: -------------------------------------------------------------------------------- 1 | const faker = require('faker') 2 | const objectId = require('bson-objectid') 3 | 4 | const randomItem = items => items[faker.random.number({ max: items.length - 1 })] 5 | 6 | const randomItems = items => [...Array(faker.random.number({ max: items.length - 1 }))].map(() => items[faker.random.number({ max: items.length - 1 })]) 7 | 8 | const createOptions = () => { 9 | const defs = { 10 | app_name: 'wedn.net', 11 | app_version: '0.1.0', 12 | app_description: 'wedn.net site', 13 | site_url: 'http://localhost:2080/', 14 | site_name: 'WEDN.NET', 15 | site_description: 'MAKE IT BETTER!', 16 | site_favicon: '/favicon.ico', 17 | site_charset: 'utf-8', 18 | site_lang: 'zh-CN', 19 | site_theme: '2016', 20 | mail_server_hostname: '', 21 | mail_server_port: '465', 22 | mail_server_secure: 'true', 23 | mail_server_name: 'WEDN.NET', 24 | mail_server_login: '', 25 | mail_server_password: '', 26 | last_updated: '2016-12-24T15:42:32' 27 | } 28 | return Object.keys(defs).map((k, i) => ({ 29 | id: objectId(), 30 | key: k, 31 | value: defs[k], 32 | enabled: true, 33 | updated: faker.date.past() 34 | })) 35 | } 36 | 37 | const createUsers = length => [...Array(length)].map(() => ({ 38 | id: objectId(), 39 | slug: faker.helpers.slugify(faker.name.firstName()), 40 | username: faker.name.firstName(), 41 | password: faker.internet.password(), 42 | email: faker.internet.email(), 43 | phone: faker.phone.phoneNumberFormat(), 44 | name: faker.name.findName(), 45 | status: randomItem(['activated', 'email-unactivated', 'phone-unactivated', 'forbidden']), 46 | roles: randomItems(['administrator', 'author', 'editor', 'contributor', 'subscriber']), 47 | created: faker.date.past(), 48 | updated: faker.date.past(), 49 | meta: { 50 | avatar: faker.image.avatar(), 51 | cover: faker.image.image(), 52 | bio: faker.lorem.sentence(), 53 | website: faker.internet.url(), 54 | location: 'Beijing, China', 55 | language: randomItem(['zh_CN', 'en_US']), 56 | last_login: faker.date.past(), 57 | last_ip: faker.internet.ip() 58 | } 59 | })) 60 | 61 | const createPosts = (length, users) => [...Array(length)].map(() => ({ 62 | id: objectId(), 63 | slug: faker.lorem.slug(), 64 | title: faker.lorem.sentence(), 65 | excerpt: faker.lorem.paragraph(), 66 | content: faker.lorem.paragraphs(), 67 | type: randomItem(['blog', 'page']), 68 | status: randomItem(['published', 'drafted']), 69 | comment_status: randomItem(['open', 'close']), 70 | comment_count: faker.random.number({ max: 100 }), 71 | view_count: faker.random.number({ max: 1000 }), 72 | user_id: randomItem(users).id, 73 | parent_id: null, 74 | created: faker.date.past(), 75 | updated: faker.date.past(), 76 | meta: {} 77 | })) 78 | 79 | const createTerms = (length, posts) => [...Array(length)].map(() => ({ 80 | id: objectId(), 81 | slug: faker.lorem.slug(), 82 | name: faker.lorem.word(), 83 | type: randomItem(['category', 'tag']), 84 | description: faker.lorem.sentence(), 85 | count: 1, 86 | parent_id: null, 87 | created: faker.date.past(), 88 | updated: faker.date.past(), 89 | relations: [ 90 | randomItem(posts).id, 91 | randomItem(posts).id, 92 | randomItem(posts).id, 93 | randomItem(posts).id 94 | ], 95 | meta: {} 96 | })) 97 | 98 | const createComments = (length, posts) => [...Array(length)].map(() => ({ 99 | id: objectId(), 100 | author: faker.name.findName(), 101 | email: faker.internet.email(), 102 | ip: faker.internet.ip(), 103 | content: faker.lorem.paragraph(), 104 | status: randomItem(['hold', 'rejected', 'approved']), 105 | user_agent: faker.internet.userAgent(), 106 | post_id: randomItem(posts).id, 107 | user_id: 0, 108 | parent_id: null, 109 | created: faker.date.past(), 110 | updated: faker.date.past(), 111 | meta: {} 112 | })) 113 | 114 | module.exports = () => { 115 | const options = createOptions() 116 | const users = createUsers(80) 117 | const posts = createPosts(50, users) 118 | const terms = createTerms(10, posts) 119 | const comments = createComments(10, posts) 120 | 121 | return { options, users, posts, terms, comments } 122 | } 123 | 124 | module.exports.fallbackUsers = [ 125 | { 126 | slug: 'zce', 127 | username: 'zce', 128 | password: 'wanglei', 129 | email: 'w@zce.me', 130 | name: 'Wang Lei', 131 | status: 'activated', 132 | roles: ['administrator'], 133 | meta: { avatar: 'https://img.zce.me/avatar/faker.svg' } 134 | }, 135 | { 136 | slug: 'admin', 137 | username: 'admin', 138 | password: 'admin', 139 | name: 'Admin', 140 | status: 'activated', 141 | roles: ['administrator'], 142 | meta: { avatar: 'https://img.zce.me/avatar/faker.svg' } 143 | }, 144 | { 145 | slug: 'demo', 146 | username: 'demo', 147 | password: 'demo', 148 | name: 'Demo', 149 | status: 'forbidden', 150 | roles: ['administrator'], 151 | meta: { avatar: 'https://img.zce.me/avatar/faker.svg' } 152 | } 153 | ] 154 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const jsonServer = require('json-server') 2 | const expressJwt = require('express-jwt') 3 | 4 | const data = require('./data') 5 | const user = require('./user') 6 | const token = require('./token') 7 | const config = require('./config') 8 | 9 | const server = jsonServer.create() 10 | const router = jsonServer.router(data.init()) 11 | 12 | // Token authorize 13 | const tokenAuthorize = expressJwt({ 14 | secret: config.secret, 15 | audience: config.audience, 16 | issuer: config.issuer, 17 | algorithms: ['HS256'], 18 | credentialsRequired: false, 19 | getToken: token.getToken, 20 | isRevoked: (req, payload, done) => done(null, data.isRevokedToken(payload)) 21 | }) 22 | 23 | // Role authorize 24 | const roleAuthorize = (req, res, next) => { 25 | if (!req.user) return res.status(401).send({ message: 'Requires authentication.' }) 26 | 27 | // TODO: delete token or logout 28 | const user = data.getUserBySlug(req.user.slug) 29 | if (user.roles.includes('administrator')) return next() 30 | res.status(403).send({ message: 'Requires administrator.' }) 31 | } 32 | 33 | // Common middlewares 34 | server.use(jsonServer.defaults()) 35 | server.use(jsonServer.bodyParser) 36 | 37 | // Delay 38 | server.use((req, res, next) => { 39 | // enable? 40 | if (!config.enableDelay) return next() 41 | // ignore options request 42 | if (req.method === 'OPTIONS') return next() 43 | setTimeout(next, Math.random() * 1000) 44 | }) 45 | 46 | // backdoor: Toggle delay action 47 | server.get('/backdoor/delay', (req, res) => { 48 | config.enableDelay = !config.enableDelay 49 | res.send(config.enableDelay) 50 | }) 51 | 52 | // backdoor: Reset database 53 | server.get('/backdoor/reset', (req, res) => { 54 | data.init() 55 | router.db.read() 56 | res.send('The database has been reset') 57 | }) 58 | 59 | // Create token 60 | server.post('/tokens', token.create) 61 | 62 | // Check token 63 | server.get('/tokens/:token?', tokenAuthorize, token.check) 64 | 65 | // Revoke token 66 | server.delete('/tokens/:token?', tokenAuthorize, token.revoke) 67 | 68 | // Get login user 69 | server.get('/users/me', tokenAuthorize, user.me) 70 | 71 | // Use default json server router 72 | server.use(tokenAuthorize, roleAuthorize, router) 73 | 74 | // Friendly error output 75 | server.use((err, req, res, next) => { 76 | if (err.name !== 'UnauthorizedError') return next(err) 77 | res.status(401).send({ message: err.message }) 78 | }) 79 | 80 | module.exports = server 81 | 82 | /** 83 | * full fake REST API with json-server 84 | * https://github.com/typicode/json-server#module 85 | * https://www.ibm.com/developerworks/cn/web/1103_chenyan_restapi/ 86 | * https://blog.jobbole.com/41233/ 87 | * https://github.com/auth0/express-jwt 88 | * https://github.com/auth0-blog/nodejs-jwt-authentication-sample 89 | * https://github.com/auth0/express-jwt 90 | * http://ngionic.com/2015/06/%E5%9F%BA%E4%BA%8Ejson%E7%9A%84web-token%E7%9A%84nodejs-api%E9%AA%8C%E8%AF%81%E5%AE%9E%E4%BE%8B/ 91 | * http://hao.jser.com/archive/8137/ 92 | * https://github.com/hokaccha/node-jwt-simple 93 | * http://www.haomou.net/2014/08/13/2014_web_token/ 94 | */ 95 | -------------------------------------------------------------------------------- /server/token.js: -------------------------------------------------------------------------------- 1 | const { v4: uuidv4 } = require('uuid') 2 | const jsonwebtoken = require('jsonwebtoken') 3 | 4 | const config = require('./config') 5 | const data = require('./data') 6 | 7 | const getToken = req => { 8 | // custom token schema 9 | if (!req.headers.authorization) return null 10 | const temp = req.headers.authorization.split(' ') 11 | const types = ['Bearer', 'JWT'] 12 | if (types.includes(temp[0])) return temp[1] 13 | if (req.params.token) return req.params.token 14 | return req.query.token 15 | } 16 | 17 | const create = (req, res) => { 18 | const { username, password } = req.body 19 | 20 | if (!username || !password) { 21 | return res.status(400).send({ message: 'You must send the username and the password.' }) 22 | } 23 | 24 | const user = data.getUserByUsername(username) 25 | 26 | // exists user 27 | if (!user) { 28 | return res.status(401).send({ message: 'Incorrect username or password.' }) 29 | } 30 | 31 | // password match 32 | if (user.password !== password) { 33 | return res.status(401).send({ message: 'Incorrect username or password.' }) 34 | } 35 | 36 | // check status 37 | if (user.status === 'forbidden') { 38 | return res.status(401).send({ message: 'Your account is forbidden.' }) 39 | } 40 | 41 | // TODO: filter user info 42 | const payload = { slug: user.slug, uuid: uuidv4() } 43 | const token = jsonwebtoken.sign(payload, config.secret, { expiresIn: config.expires, audience: config.audience, issuer: config.issuer }) 44 | 45 | res.status(201).send({ token }) 46 | } 47 | 48 | const check = (req, res) => { 49 | if (!req.user) return res.status(401).send({ message: 'Token is invalidated.' }) 50 | res.status(200).send({ message: 'Token is validated.' }) 51 | } 52 | 53 | const revoke = (req, res) => { 54 | const token = getToken(req) 55 | const payload = jsonwebtoken.decode(token) 56 | data.revokeToken(payload) 57 | res.status(200).send({ message: 'Revocation token success.' }) 58 | } 59 | 60 | module.exports = { getToken, create, check, revoke } 61 | -------------------------------------------------------------------------------- /server/user.js: -------------------------------------------------------------------------------- 1 | const data = require('./data') 2 | 3 | const me = (req, res) => { 4 | const user = data.getUserBySlug(req.user.slug) 5 | if (!user) return res.status(404).send({}) 6 | const result = Object.assign({}, user) 7 | // TODO: delete sensitive prop 8 | delete result.password 9 | res.send(result) 10 | } 11 | 12 | module.exports = { me } 13 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "alias": [ 4 | "dashboard-server", 5 | "dashboard-server.now.sh" 6 | ], 7 | "env": { 8 | "JWT_SECRET": "@jwt-secret", 9 | "JWT_ISSUER": "@jwt-issuer", 10 | "JWT_AUDIENCE": "@jwt-audience" 11 | }, 12 | "builds": [ 13 | { "src": "public/index.html", "use": "@vercel/static" }, 14 | { "src": "server/index.js", "use": "@vercel/node" } 15 | ], 16 | "routes": [ 17 | { "src": "/", "dest": "/public/index.html" }, 18 | { "src": "/index.html", "status": 301, "headers": { "Location": "/" } }, 19 | { "src": "/(.*)", "dest": "/server/index.js" } 20 | ] 21 | } 22 | --------------------------------------------------------------------------------