big oauth improvements

This commit is contained in:
les
2020-01-21 01:24:10 +01:00
parent d1dbbebffb
commit e0b3dd8d4a
18 changed files with 289 additions and 172 deletions

View File

@@ -1,37 +1,55 @@
<template lang='pug'> <template lang='pug'>
//- el-card.mt-5 el-card.mt-5
//- div(slot='header') h4(slot='header') <nuxt-link :to='"/"'><img src='/favicon.ico'/></nuxt-link> {{settings.title}} - {{$t('common.authorize')}}
//- h4 <img src='/favicon.ico'/> App authorization div
div(v-if='client')
h5 <u>{{$auth.user.email}}</u> h5 <u>{{$auth.user.email}}</u>
p External application <b>{{client.name}}</b> want following permission grants: p External application <code>{{client.name}}</code> want following permission grants:
ul ul
li(v-for="scope in $route.query.scope.split(' ')") {{scope}} li(v-for="s in scope.split(' ')") {{s}}
span You will be redirected to <b>{{$route.query.redirect_uri}}</b> span(v-if='redirect_uri!=="urn:ietf:wg:oauth:2.0:oob"') You will be redirected to <code>{{$route.query.redirect_uri}}</code>
el-row.mt-3(justify='center') el-row.mt-3(justify='center')
el-col(:span='12' :offset='6' style='text-align: center') el-col(:span='12' :offset='6' style='text-align: center')
a(:href='authorizeURL') a(:href='authorizeURL')
el-button.mr-1(plain type='success') {{$t('common.authorize')}} el-button.mr-1(plain type='success') {{$t('common.authorize')}}
a(to='/') a(href='/')
el-button.mt-1(plain type='warning') {{$t('common.cancel')}} el-button.mt-1(plain type='warning') {{$t('common.cancel')}}
</template> </template>
<script> <script>
import { mapActions, mapState } from 'vuex' import { mapState } from 'vuex'
import { Message } from 'element-ui'
import get from 'lodash/get'
export default { export default {
layout: 'modal', layout: 'modal',
name: 'Authorize', name: 'Authorize',
middleware: ['auth'], middleware: ['auth'],
async asyncData ({ $axios, query }) { async asyncData ({ $axios, query, error, req }) {
const { client_id, redirect_uri, scope, response_type } = query
let err = ''
if (!client_id) {
err = 'client_id is missing'
}
if (!redirect_uri) {
err = 'redirect_uri is missing'
}
if (!scope || scope !== 'write') {
err = 'scope is missing or wrong'
}
if (!response_type || response_type !== 'code') {
err = 'response_type is missing or wrong'
}
// retrieve client validity // retrieve client validity
try { try {
const client = await $axios.$get(`/client/${query.client_id}`) const client = await $axios.$get(`/client/${client_id}`)
return { client } if (!client) {
err = 'client not found'
}
if (err) {
return error({ statusCode: 404, message: err })
}
return { client, redirect_uri, scope, response_type }
} catch (e) { } catch (e) {
console.error(e) error({ statusCode: 400, message: 'Something goes wrong with OAuth authorization' })
} }
}, },
data () { data () {

View File

@@ -5,10 +5,10 @@
p(v-html="$t('login.description')") p(v-html="$t('login.description')")
div(v-loading='loading') div(v-loading='loading')
el-input.mb-2(v-model='email' type='email' name='email' prefix-icon='el-icon-user' el-input.mb-2(v-model='email' type='email' title='email' prefix-icon='el-icon-user'
:placeholder='$t("common.email")' autocomplete='email' ref='email') :placeholder='$t("common.email")' autocomplete='email' ref='email')
el-input.mb-1(v-model='password' @keyup.enter.native="submit" el-input.mb-1(v-model='password' @keyup.enter.native="submit"
prefix-icon='el-icon-lock' name='password' prefix-icon='el-icon-lock' name='password'
type='password' :placeholder='$t("common.password")') type='password' :placeholder='$t("common.password")')
@@ -41,6 +41,9 @@ export default {
return !this.email || !this.password return !this.email || !this.password
} }
}, },
mounted () {
this.$refs.email.focus()
},
methods: { methods: {
...mapActions(['login']), ...mapActions(['login']),
close () { close () {
@@ -63,8 +66,8 @@ export default {
try { try {
this.loading = true this.loading = true
await this.$auth.loginWith('local', { data: { email: this.email, password: this.password } }) await this.$auth.loginWith('local', { data: { email: this.email, password: this.password } })
const user = await this.$axios.$get('/auth/user') // const user = await this.$axios.$get('/auth/user')
this.$auth.setUser(user) // this.$auth.setUser(user)
this.loading = false this.loading = false
Message({ message: this.$t('login.ok'), showClose: true, type: 'success' }) Message({ message: this.$t('login.ok'), showClose: true, type: 'success' })
this.close() this.close()

View File

@@ -14,7 +14,6 @@
el-input.mb-2(v-model='user.description' type="textarea" rows='3' :placeholder="$t('common.description')") el-input.mb-2(v-model='user.description' type="textarea" rows='3' :placeholder="$t('common.description')")
span(slot='footer')
el-button(plain type="success" :disabled='disabled' @click='register') {{$t('common.send')}} <v-icon name='chevron-right'/> el-button(plain type="success" :disabled='disabled' @click='register') {{$t('common.send')}} <v-icon name='chevron-right'/>
</template> </template>

View File

@@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
el-main el-main#edit_page
h5.text-center {{edit?$t('common.edit_event'):$t('common.add_event')}} h5.text-center {{edit?$t('common.edit_event'):$t('common.add_event')}}
el-form(v-loading='loading') el-form(v-loading='loading')
@@ -18,10 +18,11 @@
//- tags //- tags
div {{$t('event.tag_description')}} div {{$t('event.tag_description')}}
el-select.mb-3(v-model='event.tags' multiple filterable client-only
@input.native='queryTags=$event.target.value' @change='queryTags=""' el-select.mb-3(v-model='event.tags' multiple filterable
allow-create default-first-option placeholder='Tag') @input.native='queryTags=$event.target.value' @change='queryTags=""'
el-option(v-for='tag in filteredTags' :key='tag' :label='tag' :value='tag') allow-create default-first-option placeholder='Tag')
el-option(v-for='tag in filteredTags' :key='tag.tag' :label='tag.tag' :value='tag.tag')
//- WHERE //- WHERE
el-divider el-divider
@@ -69,8 +70,9 @@
el-radio-button(v-for='whenPattern in whenPatterns' :label='whenPattern.key' :key='whenPatterns.key') el-radio-button(v-for='whenPattern in whenPatterns' :label='whenPattern.key' :key='whenPatterns.key')
span {{whenPattern.label}} span {{whenPattern.label}}
.text-center(inline) //- form.el-form.text-center.inline.el-form-inline
el-form-item(:label="$t('event.from')") .text-center
el-form-item(:label="$t('event.from')" width='100')
el-time-select.mr-2(ref='time_start' el-time-select.mr-2(ref='time_start'
v-model="time.start" v-model="time.start"
:picker-options="{ start: '00:00', step: '00:30', end: '24:00'}") :picker-options="{ start: '00:00', step: '00:30', end: '24:00'}")
@@ -79,7 +81,6 @@
:picker-options="{start: '00:00', step: '00:30', end: '24:00'}") :picker-options="{start: '00:00', step: '00:30', end: '24:00'}")
List(v-if='event.type==="normal" && todayEvents.length' :events='todayEvents' :title='$t("event.same_day")') List(v-if='event.type==="normal" && todayEvents.length' :events='todayEvents' :title='$t("event.same_day")')
//- el-button.float-right(@click='next' type='succes' :disabled='!couldProceed') {{$t('common.next')}}
//- MEDIA / FLYER / POSTER //- MEDIA / FLYER / POSTER
el-divider <v-icon name='image'/> {{$t('common.media')}} el-divider <v-icon name='image'/> {{$t('common.media')}}
@@ -114,18 +115,6 @@ export default {
validate ({ store }) { validate ({ store }) {
return (store.state.auth.loggedIn || store.state.settings.allow_anon_event) return (store.state.auth.loggedIn || store.state.settings.allow_anon_event)
}, },
// fetch ({ store, $axios }) {
// try {
// const now = new Date()
// const events = await $axios.$get(`/event/${now.getMonth()}/${now.getFullYear()}`)
// store.commit('setEvents', events)
// const { tags, places } = await $axios.$get('/event/meta')
// store.commit('update', { tags, places })
// } catch (e) {
// console.error('Error ', e)
// }
// moment.locale(store.state.locale)
// },
async asyncData ({ params, $axios, error, store }) { async asyncData ({ params, $axios, error, store }) {
if (params.edit) { if (params.edit) {
const data = { time: {}, event: { place: {} } } const data = { time: {}, event: { place: {} } }
@@ -267,8 +256,9 @@ export default {
filteredTags () { filteredTags () {
const queryTags = this.queryTags.toLowerCase() const queryTags = this.queryTags.toLowerCase()
return _(this.tags) return _(this.tags)
.filter(t => !this.event.tags.includes(t)) .filter(t => !this.event.tags.includes(t.tag))
.filter(t => t.includes(queryTags)) .filter(t => t.tag.includes(queryTags))
// .pick('tag')
.take(5) .take(5)
.value() .value()
}, },
@@ -418,6 +408,10 @@ i {
max-width: 600px; max-width: 600px;
} }
#edit_page .el-form-item {
display: inline-flex;
}
.el-upload, .el-upload,
.el-upload-dragger { .el-upload-dragger {
overflow: hidden; overflow: hidden;

View File

@@ -61,28 +61,29 @@ export default {
*/ */
</script> </script>
<style lang='less'> <style lang='less'>
.embed_event{
a {
transition: margin .1s;
}
a:hover {
transform: prospective(10) translateX(10);
margin-left: 5px;
}
.embed_event { .embed_event {
transition: margin .1s;
background-image: url('/favicon.ico'); background-image: url('/favicon.ico');
background-repeat: no-repeat; background-repeat: no-repeat;
background-position-x: right; background-position-x: right;
background-position-y: bottom; background-position-y: bottom;
img { background-color: #1f1f1f;
width: 150px;
object-fit: cover; display: inline-block;
object-position: top; border: 1px solid #b1a3a3;
margin-right: 5px; margin: 0px auto;
height: 100%; padding: 0px;
width: 400px;
height: 210px;
overflow: hidden;
border-radius: 10px;
// transition: all .2s;
margin: 0px;
&:hover {
transform: prospective(10) translateX(10);
margin-left: 5px;
text-decoration: none;
} }
.event-info { .event-info {
@@ -97,20 +98,13 @@ a:hover {
} }
} }
background-color: #1f1f1f; img {
display: inline-block; width: 150px;
border: 1px solid #b1a3a3; object-fit: cover;
margin: 0px auto; object-position: top;
padding: 0px; margin-right: 5px;
width: 400px; height: 100%;
height: 210px; }
overflow: hidden;
border-radius: 10px;
// transition: all .2s;
margin: 0px;
} }
}
// .embed_event:hover {
// transform: scale(1.03);
// }
</style> </style>

View File

@@ -39,8 +39,7 @@
el-menu-item(@click='showEmbed=true') <i class='el-icon-copy-document'></i> {{$t('common.embed')}} el-menu-item(@click='showEmbed=true') <i class='el-icon-copy-document'></i> {{$t('common.embed')}}
//- TODO (ics of recurrent events) //- TODO (ics of recurrent events)
//- el-menu-item(v-if='!event.recurrent') el-menu-item(v-if='!event.recurrent')
el-menu-item
a(:href='`${settings.baseurl}/api/event/${event.id}.ics`') <i class='el-icon-date'></i> {{$t('common.add_to_calendar')}} a(:href='`${settings.baseurl}/api/event/${event.id}.ics`') <i class='el-icon-date'></i> {{$t('common.add_to_calendar')}}
EventAdmin(v-if='is_mine' :event='event') EventAdmin(v-if='is_mine' :event='event')
@@ -96,7 +95,7 @@ export default {
: event.start_datetime : event.start_datetime
// const now = new Date() // const now = new Date()
// const events = await $axios.$get( // const events = await $axios.$get(
// `/event/${now.getMonth()}/${now.getFullYear()}` // `/event/${now.getMonth()}/${now.getFullYear()}`
// ) // )
// store.commit('setEvents', events) // store.commit('setEvents', events)
return { event, id: Number(id) } return { event, id: Number(id) }
@@ -281,7 +280,7 @@ export default {
await this.$axios.post('/instances/toggle_user_block', { user_id: resource.apUserApId }) await this.$axios.post('/instances/toggle_user_block', { user_id: resource.apUserApId })
Message({ message: this.$t('admin.user_blocked', { user: resource.apUserApId }), type: 'success', showClose: true }) Message({ message: this.$t('admin.user_blocked', { user: resource.apUserApId }), type: 'success', showClose: true })
}, },
async deleteResource (resource) { deleteResource (resource) {
MessageBox.confirm(this.$t('admin.delete_resource_confirm'), MessageBox.confirm(this.$t('admin.delete_resource_confirm'),
this.$t('common.confirm'), { this.$t('common.confirm'), {
confirmButtonText: this.$t('common.ok'), confirmButtonText: this.$t('common.ok'),

View File

@@ -83,18 +83,18 @@ export default {
} }
if (this.filters.places.length) { if (this.filters.places.length) {
params.push(`places=${this.filters.places}`) params.push(`places=${this.filters.places.map(p => p.id)}`)
} }
if (this.filters.tags.length) { if (this.filters.tags.length) {
params.push(`tags=${this.filters.tags}`) params.push(`tags=${this.filters.tags.map(t => t.id)}`)
} }
return `<iframe style='border: 0px; width: 100%;' src="${this.settings.baseurl}/embed/list?${params.join('&')}"></iframe>` return `<iframe style='border: 0px; width: 100%;' src="${this.settings.baseurl}/embed/list?${params.join('&')}"></iframe>`
}, },
link () { link () {
const tags = this.filters.tags.join(',') const tags = this.filters.tags.map(t => t.id).join(',')
const places = this.filters.places.join(',') const places = this.filters.places.map(p => p.id).join(',')
let query = '' let query = ''
if (tags || places) { if (tags || places) {
query = '?' query = '?'

View File

@@ -3,13 +3,12 @@
</template> </template>
<script> <script>
import Home from '~/components/Home.vue' import Home from '~/components/Home.vue'
import Nav from '~/components/Nav.vue'
import moment from 'moment-timezone' import moment from 'moment-timezone'
import { mapState } from 'vuex' import { mapState } from 'vuex'
export default { export default {
name: 'Index', name: 'Index',
components: { Nav, Home }, components: { Home },
fetch ({ store }) { fetch ({ store }) {
moment.tz.setDefault(store.state.settings.instance_timezone) moment.tz.setDefault(store.state.settings.instance_timezone)
}, },

View File

@@ -1,57 +1,117 @@
const crypto = require('crypto') const crypto = require('crypto')
const { promisify } = require('util') const { promisify } = require('util')
const randomBytes = promisify(crypto.randomBytes) const randomBytes = promisify(crypto.randomBytes)
const { oauth_client: OAuthClient, oauth_token: OAuthToken, const {
oauth_code: OAuthCode } = require('../models') oauth_client: OAuthClient, oauth_token: OAuthToken,
oauth_code: OAuthCode, user: User
} = require('../models')
const debug = require('debug')('oauth')
async function randomString(len = 16) { async function randomString (len = 16) {
const bytes = await randomBytes(len*8) const bytes = await randomBytes(len * 8)
return crypto return crypto
.createHash('sha1') .createHash('sha1')
.update(bytes) .update(bytes)
.digest('hex') .digest('hex')
} }
const oauthController = { const oauthController = {
async getClient (req, res) { // create client => http:///gancio.org/oauth#create-client
const client_id = req.params.client_id
const client = await OAuthClient.findOne({ where: { client_id }})
console.error('ma non ho trovato il client ', client_id, client )
res.json(client)
},
async createClient (req, res) { async createClient (req, res) {
debug('Create client ', req.body.client_name)
// only write scope is supported
if (req.body.scopes && req.body.scopes !== 'write') {
return res.status(422).json({ error: 'Invalid scopes' })
}
const client = { const client = {
id: await randomString(256),
name: req.body.client_name, name: req.body.client_name,
redirectUris: req.body.redirect_uris || 'urn:ietf:wg:oauth:2.0:oob', redirectUris: req.body.redirect_uris,
scopes: req.body.scopes || 'write', scopes: req.body.scopes || 'write',
client_id: await randomString(256), website: req.body.website,
client_secret: await randomString(256) client_secret: await randomString(256)
} }
res.json(await OAuthClient.create(client))
try {
await OAuthClient.create(client)
client.client_id = client.id
delete client.id
res.json(client)
} catch (e) {
debug(e)
res.status(400).json(e)
}
}, },
async associate (req, res) { async getClients (req, res) {
const { client_id, redirect_uri, response_type } = req.query const tokens = await OAuthToken.findAll({
console.error('dentro associate ', client_id, redirect_uri, response_type ) include: [{ model: User, where: { id: req.user.id } }, { model: OAuthClient, as: 'client' }],
raw: true,
nest: true
})
res.json(tokens)
}, },
model: { model: {
async getClient (clientId, clientSecret) {
console.error(`model getClient ${clientId} / ${clientSecret}`) /**
const client = await OAuthClient.findByPk(clientId) * Invoked to retrieve an existing access token previously saved through #saveToken().
client.grants = ['authorization_code'] * https://oauth2-server.readthedocs.io/en/latest/model/spec.html#getaccesstoken-accesstoken-callback
return client || false * */
async getAccessToken (accessToken) {
const oauth_token = await OAuthToken.findByPk(accessToken,
{ include: [User, { model: OAuthClient, as: 'client' }], nest: true, raw: true })
return oauth_token
}, },
async saveAuthorizationCode(code, client, user) { /**
console.error('dentro save auth code ', client, user, code) * Invoked to retrieve a client using a client id or a client id/client secret combination, depending on the grant type.
*/
async getClient (client_id, client_secret) {
const client = await OAuthClient.findByPk(client_id, { raw: true })
if (client_secret && client_secret !== client.client_secret) {
return false
}
if (client) { client.grants = ['authorization_code'] }
return client
},
async getRefreshToken (refresh_token) {
const oauth_token = await OAuthToken.findOne({ where: { refresh_token }, raw: true })
return oauth_token
},
async getAuthorizationCode (code) {
const oauth_code = await OAuthCode.findByPk(code,
{ include: [User, { type: OAuthClient, as: 'client' }], nest: true, raw: true })
return oauth_code
},
async saveToken (token, client, user) {
token.userId = user.id
token.oauthClientId = client.id
const oauth_token = await OAuthToken.create(token)
oauth_token.client = client
oauth_token.user = user
return oauth_token
},
async revokeAuthorizationCode (code) {
const oauth_code = await OAuthCode.findByPk(code)
return oauth_code.destroy()
},
async saveAuthorizationCode (code, client, user) {
code.userId = user.id
code.oauthClientId = client.id
const ret = await OAuthCode.create(code) const ret = await OAuthCode.create(code)
return ret return ret
} }
} }
} }

View File

@@ -11,6 +11,7 @@ const instanceController = require('./controller/instance')
const apUserController = require('./controller/ap_user') const apUserController = require('./controller/ap_user')
const resourceController = require('./controller/resource') const resourceController = require('./controller/resource')
const oauthController = require('./controller/oauth') const oauthController = require('./controller/oauth')
const oauth = require('./oauth')
const storage = require('./storage') const storage = require('./storage')
const upload = multer({ storage }) const upload = multer({ storage })
@@ -82,7 +83,7 @@ api.get('/event/:event_id.:format?', cors, eventController.get)
api.get('/export/:type', cors, exportController.export) api.get('/export/:type', cors, exportController.export)
// get events in this range // get events in this range
api.get('/event/:month/:year', cors, eventController.getAll) // api.get('/event/:month/:year', cors, eventController.getAll)
api.get('/event', cors, eventController.select) api.get('/event', cors, eventController.select)
api.get('/instances', isAdmin, instanceController.getAll) api.get('/instances', isAdmin, instanceController.getAll)
@@ -93,9 +94,12 @@ api.put('/resources/:resource_id', isAdmin, resourceController.hide)
api.delete('/resources/:resource_id', isAdmin, resourceController.remove) api.delete('/resources/:resource_id', isAdmin, resourceController.remove)
api.get('/resources', isAdmin, resourceController.getAll) api.get('/resources', isAdmin, resourceController.getAll)
api.get('/client/:client_id', isAuth, oauthController.getClient) api.get('/clients', isAuth, oauthController.getClients)
api.post('/client', oauthController.createClient) api.post('/client', oauthController.createClient)
// api.get('/verify', oauth.oauthServer.authenticate(), (req, res) => {
// })
// Handle 404 // Handle 404
api.use((req, res) => { api.use((req, res) => {
debug('404 Page not found: %s', req.path) debug('404 Page not found: %s', req.path)
@@ -104,7 +108,7 @@ api.use((req, res) => {
// Handle 500 // Handle 500
api.use((error, req, res, next) => { api.use((error, req, res, next) => {
debug(error) debug(error.toString())
res.status(500).send('500: Internal Server Error') res.status(500).send('500: Internal Server Error')
}) })

View File

@@ -1,19 +1,17 @@
module.exports = (sequelize, DataTypes) => { module.exports = (sequelize, DataTypes) => {
const OAuthClient = sequelize.define('oauth_client', { const OAuthClient = sequelize.define('oauth_client', {
client_id: { id: {
type: DataTypes.STRING, type: DataTypes.STRING,
primaryKey: true primaryKey: true,
allowNull: false
}, },
name: DataTypes.STRING, name: DataTypes.STRING,
scopes: DataTypes.STRING,
client_secret: DataTypes.STRING, client_secret: DataTypes.STRING,
redirectUris: DataTypes.STRING scopes: DataTypes.STRING,
redirectUris: DataTypes.STRING,
website: DataTypes.STRING
}, {}) }, {})
OAuthClient.associate = function (models) {
OAuthClient.belongsTo(models.user)
}
return OAuthClient return OAuthClient
} }

View File

@@ -5,13 +5,14 @@ module.exports = (sequelize, DataTypes) => {
type: DataTypes.STRING, type: DataTypes.STRING,
primaryKey: true primaryKey: true
}, },
expiresAt: DataTypes.DATE,
scope: DataTypes.STRING, scope: DataTypes.STRING,
redirect_uri: DataTypes.STRING redirect_uri: DataTypes.STRING
}, {}) }, {})
OAuthCode.associate = function (models) { OAuthCode.associate = function (models) {
OAuthCode.belongsTo(models.user) OAuthCode.belongsTo(models.user)
OAuthCode.belongsTo(models.oauth_client) OAuthCode.belongsTo(models.oauth_client, { as: 'client' })
} }
return OAuthCode return OAuthCode

View File

@@ -1,14 +1,30 @@
module.exports = (sequelize, DataTypes) => { module.exports = (sequelize, DataTypes) => {
const OAuthToken = sequelize.define('oauth_token', { const OAuthToken = sequelize.define('oauth_token', {
access_token: DataTypes.STRING, accessToken: {
refresh_token: DataTypes.STRING, type: DataTypes.STRING,
scope: DataTypes.STRING, allowNull: false,
primaryKey: true
},
accessTokenExpiresAt: {
type: DataTypes.DATE,
get () {
return new Date(this.getDataValue('accesTokenExpiresAt'))
}
},
refreshToken: DataTypes.STRING,
refreshTokenExpiresAt: {
type: DataTypes.DATE,
get () {
return new Date(this.getDataValue('accesTokenExpiresAt'))
}
},
scope: DataTypes.STRING
}, {}) }, {})
OAuthToken.associate = function (models) { OAuthToken.associate = function (models) {
OAuthToken.belongsTo(models.user) OAuthToken.belongsTo(models.user)
OAuthToken.belongsTo(models.oauth_client) OAuthToken.belongsTo(models.oauth_client, { as: 'client' })
} }
return OAuthToken return OAuthToken

View File

@@ -2,41 +2,36 @@ const express = require('express')
const OAuthServer = require('express-oauth-server') const OAuthServer = require('express-oauth-server')
const oauth = express.Router() const oauth = express.Router()
const oauthController = require('./controller/oauth') const oauthController = require('./controller/oauth')
const debug = require('debug')('oauth')
const oauthServer = new OAuthServer({ const oauthServer = new OAuthServer({
model: oauthController.model, model: oauthController.model,
allowEmptyState: true,
useErrorHandler: true, useErrorHandler: true,
continueMiddleware: false,
debug: true, debug: true,
authenticateHandler: { handle(req) { return req.user } } authenticateHandler: {
handle (req) {
if (!req.user) {
throw new Error('Not authenticated!')
}
return req.user
}
}
}) })
oauth.oauth = oauthServer oauth.oauthServer = oauthServer
oauth.use(express.urlencoded({ extended: false }))
oauth.use(express.json()) oauth.use(express.json())
oauth.use(express.urlencoded({ extended: false }))
// post token
oauth.post('/token', oauthServer.token()) oauth.post('/token', oauthServer.token())
oauth.get('/authorize', async (req, res, next) => { oauth.get('/authorize', oauthServer.authorize())
if (!req.user) {
return res.redirect(`/login?redirect=${req.path}&client_id=${req.query.client_id}&redirect_uri=${req.query.redirect_uri}`)
}
return oauthServer.authorize()
})
oauth.post('/authorize', (req, res, next) => {
if (!req.user) {
return res.redirect(`/login?redirect=${req.path}&client_id=${req.query.client_id}&redirect_uri=${req.query.redirect_uri}`)
}
return oauthServer.authorize()
})
oauth.use((err, req, res, next) => { oauth.use((err, req, res, next) => {
res.status(500).json(err) const error_msg = err.toString()
debug(err)
res.status(500).send(error_msg)
}) })
// oauth.post('/login', )
module.exports = oauth module.exports = oauth

View File

@@ -42,11 +42,9 @@ module.exports = {
moment.tz.setDefault(req.settings.instance_timezone) moment.tz.setDefault(req.settings.instance_timezone)
// TODO: oauth // TODO: oauth
// auth
jwt(req, res, async () => { jwt(req, res, async () => {
if (!req.user) { return next() } if (!req.user) { return next() }
req.user = await User.findOne({ req.user = await User.findOne({ where: { id: req.user.id, is_active: true } })
where: { id: req.user.id, is_active: true } })
next() next()
}) })
} }

View File

@@ -2,25 +2,18 @@
module.exports = { module.exports = {
up: (queryInterface, Sequelize) => { up: (queryInterface, Sequelize) => {
return queryInterface.createTable('oauth_clients', { return queryInterface.createTable('oauth_clients', {
client_id: { id: {
type: Sequelize.STRING, allowNull: false,
primaryKey: true primaryKey: true,
type: Sequelize.STRING
}, },
name: Sequelize.STRING, name: Sequelize.STRING,
scopes: Sequelize.STRING,
client_secret: Sequelize.STRING, client_secret: Sequelize.STRING,
scopes: Sequelize.STRING,
redirectUris: Sequelize.STRING, redirectUris: Sequelize.STRING,
website: Sequelize.STRING,
createdAt: { type: Sequelize.DATE, allowNull: false }, createdAt: { type: Sequelize.DATE, allowNull: false },
updatedAt: { type: Sequelize.DATE, allowNull: false }, updatedAt: { type: Sequelize.DATE, allowNull: false }
userId: {
type: Sequelize.INTEGER,
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
}) })
}, },

View File

@@ -6,15 +6,16 @@ module.exports = {
type: Sequelize.STRING, type: Sequelize.STRING,
primaryKey: true primaryKey: true
}, },
expiresAt: Sequelize.DATE,
scope: Sequelize.STRING, scope: Sequelize.STRING,
redirect_uri: Sequelize.STRING, redirect_uri: Sequelize.STRING,
createdAt: { type: Sequelize.DATE, allowNull: false }, createdAt: { type: Sequelize.DATE, allowNull: false },
updatedAt: { type: Sequelize.DATE, allowNull: false }, updatedAt: { type: Sequelize.DATE, allowNull: false },
oauthClientClientId: { clientId: {
type: Sequelize.INTEGER, type: Sequelize.STRING,
references: { references: {
model: 'oauth_clients', model: 'oauth_clients',
key: 'client_id' key: 'id'
}, },
onUpdate: 'CASCADE', onUpdate: 'CASCADE',
onDelete: 'CASCADE' onDelete: 'CASCADE'
@@ -27,7 +28,7 @@ module.exports = {
}, },
onUpdate: 'CASCADE', onUpdate: 'CASCADE',
onDelete: 'CASCADE' onDelete: 'CASCADE'
}, }
}) })
}, },

View File

@@ -0,0 +1,45 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('oauth_tokens', {
accessToken: {
allowNull: false,
primaryKey: true,
type: Sequelize.STRING
},
accessTokenExpiresAt: Sequelize.DATE,
refreshToken: Sequelize.STRING,
refreshTokenExpiresAt: Sequelize.DATE,
scope: Sequelize.STRING,
createdAt: { type: Sequelize.DATE, allowNull: false },
updatedAt: { type: Sequelize.DATE, allowNull: false },
clientId: {
type: Sequelize.STRING,
references: {
model: 'oauth_clients',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
userId: {
type: Sequelize.INTEGER,
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
}
})
},
down: (queryInterface, Sequelize) => {
/*
Add reverting commands here.
Return a promise to correctly handle asynchronicity.
Example:
*/
return queryInterface.dropTable('oauth_tokens')
}
}