[oauth] start oauth auth_code server implementation
This commit is contained in:
parent
c510541c50
commit
7ab81be418
17 changed files with 1631 additions and 838 deletions
|
@ -1,5 +1,8 @@
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
unreleased
|
||||||
|
- oauth server implementation
|
||||||
|
|
||||||
### 1.17.14
|
### 1.17.14
|
||||||
[locale] add catalan
|
[locale] add catalan
|
||||||
[fix] fedi outbox
|
[fix] fedi outbox
|
||||||
|
|
12
layouts/modal.vue
Normal file
12
layouts/modal.vue
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<template lang='pug'>
|
||||||
|
el-container
|
||||||
|
el-header
|
||||||
|
el-row(:gutter='20')
|
||||||
|
el-col(:span='12' :offset='6')
|
||||||
|
el-card.mt-5
|
||||||
|
h4(slot='header') <img src='/favicon.ico'/> {{$route.name}}
|
||||||
|
nuxt
|
||||||
|
</template>
|
||||||
|
<style lang="less">
|
||||||
|
@import '../assets/style.less';
|
||||||
|
</style>
|
|
@ -67,7 +67,8 @@
|
||||||
"follow": "Segui",
|
"follow": "Segui",
|
||||||
"n_resources": "nessuna risorsa|una risorsa|{n} risorse",
|
"n_resources": "nessuna risorsa|una risorsa|{n} risorse",
|
||||||
"resources": "Risorse",
|
"resources": "Risorse",
|
||||||
"moderation": "Moderazione"
|
"moderation": "Moderazione",
|
||||||
|
"authorize": "Autorizza"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"description": "Entrando puoi pubblicare nuovi eventi.",
|
"description": "Entrando puoi pubblicare nuovi eventi.",
|
||||||
|
|
33
package.json
33
package.json
|
@ -5,9 +5,9 @@
|
||||||
"author": "lesion",
|
"author": "lesion",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:nuxt": "cross-env NODE_ENV=development nuxt dev",
|
"dev:nuxt": "cross-env NODE_ENV=development nuxt dev",
|
||||||
"dev": "cross-env DEBUG=*,-babel,-follow-redirects,-send,-body-parser:*,-express:*,-connect:*,-sequelize:* NODE_ENV=development node server/index.js",
|
"dev": "cross-env DEBUG=*,-babel*,-follow-redirects,-send,-body-parser:*,-express:*,-connect:*,-sequelize:* NODE_ENV=development node server/index.js",
|
||||||
"build": "nuxt build",
|
"build": "nuxt build",
|
||||||
"start": "cross-env DEBUG=*,-babel,-follow-redirects,-send,-body-parser:*,-express:*,-connect:*,-sequelize:* NODE_ENV=production node server/cli.js",
|
"start": "cross-env DEBUG=*,-babel*,-follow-redirects,-send,-body-parser:*,-express:*,-connect:*,-sequelize:* NODE_ENV=production node server/cli.js",
|
||||||
"lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
|
"lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
|
||||||
"doc": "cd docs && bundle exec jekyll b",
|
"doc": "cd docs && bundle exec jekyll b",
|
||||||
"doc:dev": "cd docs && bundle exec jekyll s --drafts",
|
"doc:dev": "cd docs && bundle exec jekyll s --drafts",
|
||||||
|
@ -50,26 +50,27 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/auth": "^4.8.4",
|
"@nuxtjs/auth": "^4.8.4",
|
||||||
"@nuxtjs/axios": "^5.8.0",
|
"@nuxtjs/axios": "^5.9.0",
|
||||||
"accept-language": "^3.0.18",
|
"accept-language": "^3.0.18",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"body-parser": "^1.18.3",
|
"body-parser": "^1.18.3",
|
||||||
"bootstrap": "^4.4.1",
|
"bootstrap": "^4.4.1",
|
||||||
"config": "^3.2.4",
|
"config": "^3.2.4",
|
||||||
"consola": "^2.11.0",
|
"consola": "^2.11.1",
|
||||||
"cookie-parser": "^1.4.4",
|
"cookie-parser": "^1.4.4",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cross-env": "^6.0.0",
|
"cross-env": "^6.0.0",
|
||||||
"dayjs": "^1.8.17",
|
"dayjs": "^1.8.18",
|
||||||
"element-ui": "^2.13.0",
|
"element-ui": "^2.13.0",
|
||||||
"email-templates": "^6.0.6",
|
"email-templates": "^6.1.1",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-jwt": "^5.3.1",
|
"express-jwt": "^5.3.1",
|
||||||
"express-middleware-log": "^1.2.0",
|
"express-middleware-log": "^1.2.0",
|
||||||
|
"express-oauth-server": "^2.0.0",
|
||||||
"http-signature": "^1.3.1",
|
"http-signature": "^1.3.1",
|
||||||
"ics": "^2.16.0",
|
"ics": "^2.16.0",
|
||||||
"inquirer": "^7.0.0",
|
"inquirer": "^7.0.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"less": "^3.10.3",
|
"less": "^3.10.3",
|
||||||
"lodash": "^4.17.14",
|
"lodash": "^4.17.14",
|
||||||
|
@ -78,20 +79,20 @@
|
||||||
"morgan": "^1.9.1",
|
"morgan": "^1.9.1",
|
||||||
"multer": "^1.4.2",
|
"multer": "^1.4.2",
|
||||||
"node-fetch": "^2.6.0",
|
"node-fetch": "^2.6.0",
|
||||||
"nuxt": "^2.10.2",
|
"nuxt": "^2.11.0",
|
||||||
"nuxt-express-module": "^0.0.11",
|
"nuxt-express-module": "^0.0.11",
|
||||||
"pg": "^7.14.0",
|
"pg": "^7.15.1",
|
||||||
"sanitize-html": "^1.20.1",
|
"sanitize-html": "^1.20.1",
|
||||||
"sass-loader": "^8.0.0",
|
"sass-loader": "^8.0.0",
|
||||||
"sequelize": "^5.21.2",
|
"sequelize": "^5.21.3",
|
||||||
"sequelize-cli": "^5.5.1",
|
"sequelize-cli": "^5.5.1",
|
||||||
"sharp": "^0.23.4",
|
"sharp": "^0.23.4",
|
||||||
"sqlite3": "^4.1.1",
|
"sqlite3": "^4.1.1",
|
||||||
"url": "^0.11.0",
|
"url": "^0.11.0",
|
||||||
"v-calendar": "^1.0.0-beta.16",
|
"v-calendar": "^1.0.0-beta.23",
|
||||||
"vue-awesome": "^4.0.0",
|
"vue-awesome": "^4.0.0",
|
||||||
"vue-clipboard2": "^0.3.1",
|
"vue-clipboard2": "^0.3.1",
|
||||||
"vue-i18n": "^8.15.1",
|
"vue-i18n": "^8.15.3",
|
||||||
"yargs": "^15.0.2"
|
"yargs": "^15.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -100,18 +101,18 @@
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-config-prettier": "^6.7.0",
|
"eslint-config-prettier": "^6.7.0",
|
||||||
"eslint-config-standard": ">=14.1.0",
|
"eslint-config-standard": ">=14.1.0",
|
||||||
"eslint-loader": "^3.0.0",
|
"eslint-loader": "^3.0.3",
|
||||||
"eslint-plugin-import": ">=2.17.3",
|
"eslint-plugin-import": ">=2.19.1",
|
||||||
"eslint-plugin-jest": ">=23.1.1",
|
"eslint-plugin-jest": ">=23.1.1",
|
||||||
"eslint-plugin-node": ">=10.0.0",
|
"eslint-plugin-node": ">=10.0.0",
|
||||||
"eslint-plugin-nuxt": ">=0.5.0",
|
"eslint-plugin-nuxt": ">=0.5.0",
|
||||||
"eslint-plugin-prettier": "^3.1.1",
|
"eslint-plugin-prettier": "^3.1.2",
|
||||||
"eslint-plugin-promise": ">=4.0.1",
|
"eslint-plugin-promise": ">=4.0.1",
|
||||||
"eslint-plugin-standard": ">=4.0.1",
|
"eslint-plugin-standard": ">=4.0.1",
|
||||||
"eslint-plugin-vue": "^6.0.1",
|
"eslint-plugin-vue": "^6.0.1",
|
||||||
"jsdoc": "^3.6.3",
|
"jsdoc": "^3.6.3",
|
||||||
"less-loader": "^5.0.0",
|
"less-loader": "^5.0.0",
|
||||||
"nodemon": "^2.0.1",
|
"nodemon": "^2.0.2",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
"pug-plain-loader": "^1.0.0",
|
"pug-plain-loader": "^1.0.0",
|
||||||
"webpack-cli": "^3.3.10"
|
"webpack-cli": "^3.3.10"
|
||||||
|
|
52
pages/Authorize.vue
Normal file
52
pages/Authorize.vue
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<template lang='pug'>
|
||||||
|
//- el-card.mt-5
|
||||||
|
//- div(slot='header')
|
||||||
|
//- h4 <img src='/favicon.ico'/> App authorization
|
||||||
|
div(v-if='client')
|
||||||
|
h5 <u>{{$auth.user.email}}</u>
|
||||||
|
p External application <b>{{client.name}}</b> want following permission grants:
|
||||||
|
ul
|
||||||
|
li(v-for="scope in $route.query.scope.split(' ')") {{scope}}
|
||||||
|
span You will be redirected to <b>{{$route.query.redirect_uri}}</b>
|
||||||
|
el-row.mt-3(justify='center')
|
||||||
|
el-col(:span='12' :offset='6' style='text-align: center')
|
||||||
|
a(:href='authorizeURL')
|
||||||
|
el-button.mr-1(plain type='success') {{$t('common.authorize')}}
|
||||||
|
a(to='/')
|
||||||
|
el-button.mt-1(plain type='warning') {{$t('common.cancel')}}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapActions, mapState } from 'vuex'
|
||||||
|
import { Message } from 'element-ui'
|
||||||
|
import get from 'lodash/get'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
layout: 'modal',
|
||||||
|
name: 'Authorize',
|
||||||
|
middleware: ['auth'],
|
||||||
|
head: { title: 'Authorize' },
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
client: { name: 'Test' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async asyncData ({ $axios, query }) {
|
||||||
|
// retrieve client validity
|
||||||
|
try {
|
||||||
|
const client = await $axios.$get(`/client/${query.client_id}`)
|
||||||
|
return { client }
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['settings']),
|
||||||
|
authorizeURL () {
|
||||||
|
const { scope, response_type, client_id, redirect_uri, state } = this.$route.query
|
||||||
|
const query = `client_id=${client_id}&response_type=${response_type}&scope=${scope}&redirect_uri=${redirect_uri}&state=${state}`
|
||||||
|
return `oauth/authorize?${query}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -31,11 +31,13 @@
|
||||||
el-tab-pane
|
el-tab-pane
|
||||||
span(slot='label') <v-icon name='map-marker-alt'/> {{$t('common.where')}}
|
span(slot='label') <v-icon name='map-marker-alt'/> {{$t('common.where')}}
|
||||||
p(v-html="$t('event.where_description')")
|
p(v-html="$t('event.where_description')")
|
||||||
|
span {{event.place.name}}
|
||||||
el-select.mb-3(v-model='event.place.name'
|
el-select.mb-3(v-model='event.place.name'
|
||||||
|
@keypress.tab='testBlur'
|
||||||
@change='placeChoosed'
|
@change='placeChoosed'
|
||||||
filterable allow-create
|
filterable allow-create
|
||||||
default-first-option
|
default-first-option
|
||||||
|
@blur='testBlur'
|
||||||
)
|
)
|
||||||
el-option(v-for='place in places' :label='place.name' :value='place.name' :key='place.id')
|
el-option(v-for='place in places' :label='place.name' :value='place.name' :key='place.id')
|
||||||
div {{$t("common.address")}}
|
div {{$t("common.address")}}
|
||||||
|
@ -92,6 +94,8 @@
|
||||||
//- MEDIA / FLYER / POSTER
|
//- MEDIA / FLYER / POSTER
|
||||||
el-tab-pane
|
el-tab-pane
|
||||||
span(slot='label') {{$t('common.media')}} <v-icon name='image'/>
|
span(slot='label') {{$t('common.media')}} <v-icon name='image'/>
|
||||||
|
div.mb-2 {{$t('event.media_description')}}
|
||||||
|
img(:src='mediaUrl' @load='imageLoaded')
|
||||||
el-upload.text-center(
|
el-upload.text-center(
|
||||||
action=''
|
action=''
|
||||||
:limit="1"
|
:limit="1"
|
||||||
|
@ -100,12 +104,10 @@
|
||||||
accept='image/*'
|
accept='image/*'
|
||||||
:on-remove='cleanFile'
|
:on-remove='cleanFile'
|
||||||
:on-change='uploadedFile'
|
:on-change='uploadedFile'
|
||||||
:multiple='false'
|
:multiple='false')
|
||||||
:file-list="fileList"
|
|
||||||
)
|
|
||||||
i.el-icon-upload
|
i.el-icon-upload
|
||||||
div.el-upload__text {{$t('event.media_description')}}
|
el-input(v-model='mediaUrl' @blur='checkMediaUrl')
|
||||||
el-button.float-right(@click='done' :disabled='!couldProceed') {{edit?$t('common.edit'):$t('common.send')}}
|
el-button.mt-2.float-right(@click='done' :disabled='!couldProceed') {{edit?$t('common.edit'):$t('common.send')}}
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
@ -148,7 +150,8 @@ export default {
|
||||||
date: null,
|
date: null,
|
||||||
time: { start: '20:00', end: null },
|
time: { start: '20:00', end: null },
|
||||||
edit: false,
|
edit: false,
|
||||||
loading: false
|
loading: false,
|
||||||
|
mediaUrl: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -311,6 +314,33 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['addEvent', 'updateEvent', 'updateMeta', 'updateEvents']),
|
...mapActions(['addEvent', 'updateEvent', 'updateMeta', 'updateEvents']),
|
||||||
|
testBlur (e) {
|
||||||
|
console.error('nel blur!')
|
||||||
|
this.event.place.name = e.target.value
|
||||||
|
},
|
||||||
|
imageLoaded () {
|
||||||
|
console.error('image loaded!')
|
||||||
|
},
|
||||||
|
async checkMediaUrl () {
|
||||||
|
// if (!this.mediaUrl) return
|
||||||
|
// this.fileList.push({ name: this.mediaUrl, url: this.mediaUrl })
|
||||||
|
// const img = document.createElement('img')
|
||||||
|
// const c = document.createElement('canvas')
|
||||||
|
// const ctx = c.getContext('2d')
|
||||||
|
// img.crossOrigin = ''
|
||||||
|
// img.src = this.mediaUrl
|
||||||
|
// console.error('image loaded')
|
||||||
|
// c.width = this.naturalWidth
|
||||||
|
// c.height = this.naturalHeight
|
||||||
|
// ctx.drawImage(this, 0, 0)
|
||||||
|
// c.toBlob( raw => {
|
||||||
|
// this.event.image = { name: this.mediaUrl, raw}
|
||||||
|
// }, 'image/jpeg', 0.9)
|
||||||
|
// }
|
||||||
|
// fetch(this.mediaUrl, { mode: 'cors' })
|
||||||
|
// .then( ret => ret.blob() )
|
||||||
|
// .then( raw => { if (!raw) return; this.event.image = { name: this.mediaUrl, raw }})
|
||||||
|
},
|
||||||
recurrentDays () {
|
recurrentDays () {
|
||||||
if (this.event.type !== 'recurrent' || !this.date || !this.date.length) { return }
|
if (this.event.type !== 'recurrent' || !this.date || !this.date.length) { return }
|
||||||
const type = this.event.recurrent.type
|
const type = this.event.recurrent.type
|
||||||
|
@ -326,6 +356,7 @@ export default {
|
||||||
this.activeTab = String(Number(this.activeTab - 1))
|
this.activeTab = String(Number(this.activeTab - 1))
|
||||||
},
|
},
|
||||||
placeChoosed () {
|
placeChoosed () {
|
||||||
|
console.error('dentro place choosed')
|
||||||
const place = this.places.find(p => p.name === this.event.place.name)
|
const place = this.places.find(p => p.name === this.event.place.name)
|
||||||
if (place && place.address) {
|
if (place && place.address) {
|
||||||
this.event.place.address = place.address
|
this.event.place.address = place.address
|
||||||
|
|
58
server/api/controller/oauth.js
Normal file
58
server/api/controller/oauth.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
const crypto = require('crypto')
|
||||||
|
const { promisify } = require('util')
|
||||||
|
const randomBytes = promisify(crypto.randomBytes)
|
||||||
|
const { oauth_client: OAuthClient, oauth_token: OAuthToken,
|
||||||
|
oauth_code: OAuthCode } = require('../models')
|
||||||
|
|
||||||
|
async function randomString(len = 16) {
|
||||||
|
const bytes = await randomBytes(len*8)
|
||||||
|
return crypto
|
||||||
|
.createHash('sha1')
|
||||||
|
.update(bytes)
|
||||||
|
.digest('hex')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const oauthController = {
|
||||||
|
|
||||||
|
async getClient (req, res) {
|
||||||
|
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) {
|
||||||
|
|
||||||
|
const client = {
|
||||||
|
name: req.body.client_name,
|
||||||
|
redirectUris: req.body.redirect_uris || 'urn:ietf:wg:oauth:2.0:oob',
|
||||||
|
scopes: req.body.scopes || 'write',
|
||||||
|
client_id: await randomString(256),
|
||||||
|
client_secret: await randomString(256)
|
||||||
|
}
|
||||||
|
res.json(await OAuthClient.create(client))
|
||||||
|
},
|
||||||
|
|
||||||
|
async associate (req, res) {
|
||||||
|
const { client_id, redirect_uri, response_type } = req.query
|
||||||
|
console.error('dentro associate ', client_id, redirect_uri, response_type )
|
||||||
|
},
|
||||||
|
|
||||||
|
model: {
|
||||||
|
async getClient (clientId, clientSecret) {
|
||||||
|
console.error(`model getClient ${clientId} / ${clientSecret}`)
|
||||||
|
const client = await OAuthClient.findByPk(clientId)
|
||||||
|
client.grants = ['authorization_code']
|
||||||
|
return client || false
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveAuthorizationCode(code, client, user) {
|
||||||
|
console.error('dentro save auth code ', client, user, code)
|
||||||
|
const ret = await OAuthCode.create(code)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = oauthController
|
|
@ -1,6 +1,5 @@
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const multer = require('multer')
|
const multer = require('multer')
|
||||||
const cookieParser = require('cookie-parser')
|
|
||||||
const bodyParser = require('body-parser')
|
const bodyParser = require('body-parser')
|
||||||
const cors = require('cors')()
|
const cors = require('cors')()
|
||||||
|
|
||||||
|
@ -12,6 +11,7 @@ const settingsController = require('./controller/settings')
|
||||||
const instanceController = require('./controller/instance')
|
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 storage = require('./storage')
|
const storage = require('./storage')
|
||||||
const upload = multer({ storage })
|
const upload = multer({ storage })
|
||||||
|
@ -19,9 +19,8 @@ const upload = multer({ storage })
|
||||||
const debug = require('debug')('api')
|
const debug = require('debug')('api')
|
||||||
|
|
||||||
const api = express.Router()
|
const api = express.Router()
|
||||||
api.use(cookieParser())
|
api.use(express.urlencoded({ extended: false }))
|
||||||
api.use(bodyParser.urlencoded({ extended: false }))
|
api.use(express.json())
|
||||||
api.use(bodyParser.json())
|
|
||||||
|
|
||||||
// AUTH
|
// AUTH
|
||||||
api.post('/auth/login', userController.login)
|
api.post('/auth/login', userController.login)
|
||||||
|
@ -94,6 +93,9 @@ 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.post('/client', oauthController.createClient)
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
19
server/api/models/oauth_client.js
Normal file
19
server/api/models/oauth_client.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
const OAuthClient = sequelize.define('oauth_client', {
|
||||||
|
client_id: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
name: DataTypes.STRING,
|
||||||
|
scopes: DataTypes.STRING,
|
||||||
|
client_secret: DataTypes.STRING,
|
||||||
|
redirectUris: DataTypes.STRING
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
OAuthClient.associate = function (models) {
|
||||||
|
OAuthClient.belongsTo(models.user)
|
||||||
|
}
|
||||||
|
|
||||||
|
return OAuthClient
|
||||||
|
}
|
18
server/api/models/oauth_code.js
Normal file
18
server/api/models/oauth_code.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
const OAuthCode = sequelize.define('oauth_code', {
|
||||||
|
authorizationCode: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
scope: DataTypes.STRING,
|
||||||
|
redirect_uri: DataTypes.STRING
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
OAuthCode.associate = function (models) {
|
||||||
|
OAuthCode.belongsTo(models.user)
|
||||||
|
OAuthCode.belongsTo(models.oauth_client)
|
||||||
|
}
|
||||||
|
|
||||||
|
return OAuthCode
|
||||||
|
}
|
15
server/api/models/oauth_token.js
Normal file
15
server/api/models/oauth_token.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
const OAuthToken = sequelize.define('oauth_token', {
|
||||||
|
access_token: DataTypes.STRING,
|
||||||
|
refresh_token: DataTypes.STRING,
|
||||||
|
scope: DataTypes.STRING,
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
OAuthToken.associate = function (models) {
|
||||||
|
OAuthToken.belongsTo(models.user)
|
||||||
|
OAuthToken.belongsTo(models.oauth_client)
|
||||||
|
}
|
||||||
|
|
||||||
|
return OAuthToken
|
||||||
|
}
|
74
server/api/oauth.js
Normal file
74
server/api/oauth.js
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
const express = require('express')
|
||||||
|
const OAuthServer = require('express-oauth-server')
|
||||||
|
const oauth = express.Router()
|
||||||
|
const bodyParser = require('body-parser')
|
||||||
|
const oauthController = require('./controller/oauth')
|
||||||
|
|
||||||
|
const oauthServer = new OAuthServer({
|
||||||
|
model: oauthController.model,
|
||||||
|
useErrorHandler: true,
|
||||||
|
debug: true,
|
||||||
|
authenticateHandler: { handle(req) { return req.user } }
|
||||||
|
})
|
||||||
|
|
||||||
|
oauth.oauth = oauthServer
|
||||||
|
oauth.use(bodyParser.json())
|
||||||
|
oauth.use(bodyParser.urlencoded({ extended: false }))
|
||||||
|
|
||||||
|
// post token
|
||||||
|
// oauth.post(oauthServer.authorize())
|
||||||
|
oauth.post('/token', (req, res, next) => {
|
||||||
|
return oauthServer.token()(req, res, next)
|
||||||
|
.then(code => {
|
||||||
|
console.error('dopo il token', code)
|
||||||
|
})
|
||||||
|
.catch(e => console.error('nel catch ', e))
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create a new application
|
||||||
|
*/
|
||||||
|
oauth.get('/authorize', async (req, res, next) => {
|
||||||
|
if (!req.user) {
|
||||||
|
return res.redirect(`/?ref=login&redirect=${req.path}&client_id=${req.query.client_id}&redirect_uri=${req.query.redirect_uri}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return oauthServer.authorize()(req, res, next).then(code => {
|
||||||
|
console.error('dentro authorize?', code)
|
||||||
|
console.error(req.locals)
|
||||||
|
return
|
||||||
|
// return res.redirect(`/?ref=authorize&client_id=${req.query.client_id}&redirect_uri=${req.query.redirect_uri}&code=${code}`)
|
||||||
|
}).catch(e => { console.error('porcodio catch ', e) })
|
||||||
|
})
|
||||||
|
|
||||||
|
oauth.post('/authorize', (req, res, next) => {
|
||||||
|
if (!req.user) {
|
||||||
|
return res.redirect(`/?ref=login&redirect=${req.path}&client_id=${req.query.client_id}&redirect_uri=${req.query.redirect_uri}`)
|
||||||
|
}
|
||||||
|
console.error('sono nel post di authorize!')
|
||||||
|
const ret = oauthServer.authorize()
|
||||||
|
console.error('PORCODIO ', ret)
|
||||||
|
return ret(req, res, next).then(code => {
|
||||||
|
console.error('DAJE CHE ARRIVO QUI ', code)
|
||||||
|
console.error(req.locals)
|
||||||
|
next()
|
||||||
|
}).catch(e => console.error('CATCH ', e))
|
||||||
|
})
|
||||||
|
|
||||||
|
oauth.get('/login', (req, res) => {
|
||||||
|
res.render('login', {
|
||||||
|
client_id: req.query.client_id,
|
||||||
|
redirect_uri: req.query.redirect_uri,
|
||||||
|
redirect: req.query.redirect,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
oauth.use((err, req, res, next) => {
|
||||||
|
res.status(400).json(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
// oauth.post('/login', )
|
||||||
|
|
||||||
|
module.exports = oauth
|
30
server/migrations/20191226001504-oauth_client.js
Normal file
30
server/migrations/20191226001504-oauth_client.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface, Sequelize) => {
|
||||||
|
return queryInterface.createTable('oauth_clients', {
|
||||||
|
client_id: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
name: Sequelize.STRING,
|
||||||
|
scopes: Sequelize.STRING,
|
||||||
|
client_secret: Sequelize.STRING,
|
||||||
|
redirectUris: Sequelize.STRING,
|
||||||
|
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||||
|
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||||
|
userId: {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
references: {
|
||||||
|
model: 'users',
|
||||||
|
key: 'id'
|
||||||
|
},
|
||||||
|
onUpdate: 'CASCADE',
|
||||||
|
onDelete: 'CASCADE'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface, Sequelize) => {
|
||||||
|
return queryInterface.dropTable('oauth_clients')
|
||||||
|
}
|
||||||
|
}
|
37
server/migrations/20191226102934-oauth_code.js
Normal file
37
server/migrations/20191226102934-oauth_code.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: (queryInterface, Sequelize) => {
|
||||||
|
return queryInterface.createTable('oauth_codes', {
|
||||||
|
authorizationCode: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
scope: Sequelize.STRING,
|
||||||
|
redirect_uri: Sequelize.STRING,
|
||||||
|
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||||
|
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||||
|
oauthClientClientId: {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
references: {
|
||||||
|
model: 'oauth_clients',
|
||||||
|
key: 'client_id'
|
||||||
|
},
|
||||||
|
onUpdate: 'CASCADE',
|
||||||
|
onDelete: 'CASCADE'
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
references: {
|
||||||
|
model: 'users',
|
||||||
|
key: 'id'
|
||||||
|
},
|
||||||
|
onUpdate: 'CASCADE',
|
||||||
|
onDelete: 'CASCADE'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
down: (queryInterface, Sequelize) => {
|
||||||
|
return queryInterface.dropTable('oauth_codes')
|
||||||
|
}
|
||||||
|
}
|
84
server/model.js
Normal file
84
server/model.js
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
const crypto = require('crypto')
|
||||||
|
const { promisify } = require('util')
|
||||||
|
const randomBytes = promisify(crypto.randomBytes)
|
||||||
|
|
||||||
|
async function randomString(len = 16) {
|
||||||
|
const bytes = await randomBytes(len*8)
|
||||||
|
return crypto
|
||||||
|
.createHash('sha1')
|
||||||
|
.update(bytes)
|
||||||
|
.digest('hex')
|
||||||
|
}
|
||||||
|
|
||||||
|
const OAuth = {
|
||||||
|
clients: [
|
||||||
|
{ clientId : 'confidentialApplication', clientSecret : 'topSecret',
|
||||||
|
redirectUris : ['https://localhost:13120/asdf', 'https://example-app.com/callback', 'https://oauthdebugger.com/debug'],
|
||||||
|
grants: ['password', 'authorization_code', 'client_credentials']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientId: '1766891b7fb5fda4235dc7f0dde70fcd783371c2', clientSecret: 'ed6fdc050a415f178f2ac8428b76734edef75e5c',
|
||||||
|
grants: ['authorization_code'], redirectUris: ['urn:ietf:wg:oauth:2.0:oob'], scopes: ['write'], state: 'a'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tokens: [],
|
||||||
|
users: [{ id : '123', username: 'thomseddon', password: 'nightworld' }],
|
||||||
|
|
||||||
|
getAccessToken (bearerToken) {
|
||||||
|
console.error('dentro get access token', bearerToken, OAuth.tokens)
|
||||||
|
const tokens = OAuth.tokens.filter(token => token.accessToken === bearerToken)
|
||||||
|
return tokens.length ? tokens[0] : false
|
||||||
|
},
|
||||||
|
verifyScope (accessToken, scope) {
|
||||||
|
console.error('dentro verify scope', scope)
|
||||||
|
},
|
||||||
|
getRefreshToken (bearerToken) {
|
||||||
|
console.error('dentro refresh token')
|
||||||
|
const tokens = OAuth.tokens.filter( token => token.refreshToken === bearerToken )
|
||||||
|
return tokens.length ? tokens[0] : false
|
||||||
|
},
|
||||||
|
getClientCredentials () {
|
||||||
|
console.error('dentro get client credentials')
|
||||||
|
},
|
||||||
|
getClient (clientId, clientSecret) {
|
||||||
|
console.error(`getClient ${clientId} / ${clientSecret}`)
|
||||||
|
const clients = OAuth.clients.filter( client => client.clientId === clientId)
|
||||||
|
console.error(clients)
|
||||||
|
return clients.length ? clients[0] : false
|
||||||
|
},
|
||||||
|
getAuthorizationCode(authorizationCode) {
|
||||||
|
console.error('get auth code')
|
||||||
|
},
|
||||||
|
revokeAuthorizationCode (code) {
|
||||||
|
console.error('dentro revoke auth code ', code)
|
||||||
|
},
|
||||||
|
async createClient (client) {
|
||||||
|
client.client_id = await randomString(256)
|
||||||
|
client.client_secret = await randomString(256)
|
||||||
|
OAuth.clients.push(client)
|
||||||
|
return client
|
||||||
|
},
|
||||||
|
saveAuthorizationCode(code, client, user) {
|
||||||
|
console.error('dentro save auth code')
|
||||||
|
const ret = {
|
||||||
|
...code,
|
||||||
|
user,
|
||||||
|
client
|
||||||
|
}
|
||||||
|
OAuth.tokens.push(ret)
|
||||||
|
console.error('DIOCANEEEE salvo auth code!', OAuth.tokens)
|
||||||
|
return ret
|
||||||
|
},
|
||||||
|
saveToken (token) {
|
||||||
|
console.error('dentro save token')
|
||||||
|
},
|
||||||
|
// saveAuthorizationCode (token, client, user) {
|
||||||
|
// console.error('dentro save auth code')
|
||||||
|
// return true
|
||||||
|
// },
|
||||||
|
getUser (username, password) {
|
||||||
|
console.error('dentro get user')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = OAuth
|
|
@ -3,6 +3,7 @@ const config = require('config')
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const cors = require('cors')
|
const cors = require('cors')
|
||||||
const api = require('./api')
|
const api = require('./api')
|
||||||
|
const oauth = require('./api/oauth')
|
||||||
const cookieParser = require('cookie-parser')
|
const cookieParser = require('cookie-parser')
|
||||||
const federation = require('./federation')
|
const federation = require('./federation')
|
||||||
const webfinger = require('./federation/webfinger')
|
const webfinger = require('./federation/webfinger')
|
||||||
|
@ -10,42 +11,44 @@ const { spamFilter } = require('./federation/helpers')
|
||||||
const debug = require('debug')('routes')
|
const debug = require('debug')('routes')
|
||||||
const exportController = require('./api/controller/export')
|
const exportController = require('./api/controller/export')
|
||||||
const helpers = require('./helpers')
|
const helpers = require('./helpers')
|
||||||
const router = express.Router()
|
const app = express()
|
||||||
|
|
||||||
router.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
debug(req.path)
|
debug(req.path)
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
// ignore unimplemented ping url from fediverse
|
// ignore unimplemented ping url from fediverse
|
||||||
router.use(spamFilter)
|
app.use(spamFilter)
|
||||||
|
|
||||||
// serve favicon and static content
|
// serve favicon and static content
|
||||||
router.use('/favicon.ico', express.static(path.resolve(config.favicon || './assets/favicon.ico')))
|
app.use('/favicon.ico', express.static(path.resolve(config.favicon || './assets/favicon.ico')))
|
||||||
router.use('/logo.png', express.static('./static/gancio.png'))
|
app.use('/logo.png', express.static('./static/gancio.png'))
|
||||||
router.use('/media/', express.static(config.upload_path))
|
app.use('/media/', express.static(config.upload_path))
|
||||||
|
|
||||||
// get instance settings
|
// get instance settings
|
||||||
router.use(cookieParser())
|
app.use(cookieParser())
|
||||||
router.use(helpers.initMiddleware)
|
app.use(helpers.initMiddleware)
|
||||||
|
|
||||||
|
app.use('/oauth', oauth)
|
||||||
|
|
||||||
// rss/ics/atom feed
|
// rss/ics/atom feed
|
||||||
router.get('/feed/:type', cors(), exportController.export)
|
app.get('/feed/:type', cors(), exportController.export)
|
||||||
|
|
||||||
// api!
|
// api!
|
||||||
router.use('/api', api)
|
app.use('/api', api)
|
||||||
|
|
||||||
// federation api / activitypub / webfinger / nodeinfo
|
// federation api / activitypub / webfinger / nodeinfo
|
||||||
router.use('/.well-known', webfinger)
|
app.use('/.well-known', webfinger)
|
||||||
router.use('/federation', federation)
|
app.use('/federation', federation)
|
||||||
|
|
||||||
// Handle 500
|
// // Handle 500
|
||||||
router.use((error, req, res, next) => {
|
// app.use((error, req, res, next) => {
|
||||||
debug('Error 500: %s', error)
|
// debug('Error 500: %s', error)
|
||||||
res.status(500).send('500: Internal Server Error')
|
// res.status(500).send('500: Internal Server Error')
|
||||||
})
|
// })
|
||||||
|
|
||||||
// remaining request goes to nuxt
|
// remaining request goes to nuxt
|
||||||
// first nuxt component is ./pages/index.vue
|
// first nuxt component is ./pages/index.vue
|
||||||
|
|
||||||
module.exports = router
|
module.exports = app
|
||||||
|
|
Loading…
Reference in a new issue