Clean up OAuth2 provider code, support multiple request types in preparation for OIDC
This commit is contained in:
parent
2a7aec1b46
commit
51fd91e3db
@ -2,151 +2,152 @@ import error from '../error'
|
|||||||
import response from '../response'
|
import response from '../response'
|
||||||
import model from '../model'
|
import model from '../model'
|
||||||
import authorization from './code'
|
import authorization from './code'
|
||||||
import wrap from '../../../../scripts/asyncRoute'
|
import wrap from '../wrap'
|
||||||
|
|
||||||
const usermodel = model.user
|
|
||||||
|
|
||||||
module.exports = wrap(async (req, res, next) => {
|
module.exports = wrap(async (req, res, next) => {
|
||||||
let clientId = null
|
let clientId = null
|
||||||
let redirectUri = null
|
let redirectUri = null
|
||||||
let responseType = null
|
let responseType = null
|
||||||
let grantType = null
|
let grantTypes = []
|
||||||
let scope = null
|
let scope = null
|
||||||
let user = null
|
let user = null
|
||||||
|
|
||||||
if (!req.query.redirect_uri) {
|
if (!req.query.redirect_uri) {
|
||||||
return response.error(req, res, new error.InvalidRequest('redirect_uri field is mandatory for authorization endpoint'), redirectUri)
|
throw new error.InvalidRequest('redirect_uri field is mandatory for authorization endpoint')
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectUri = req.query.redirect_uri
|
redirectUri = req.query.redirect_uri
|
||||||
console.debug('Parameter redirect uri is', redirectUri)
|
console.debug('Parameter redirect uri is', redirectUri)
|
||||||
|
|
||||||
if (!req.query.client_id) {
|
if (!req.query.client_id) {
|
||||||
return response.error(req, res, new error.InvalidRequest('client_id field is mandatory for authorization endpoint'), redirectUri)
|
throw new error.InvalidRequest('client_id field is mandatory for authorization endpoint')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for client_secret (prevent passing it)
|
// Check for client_secret (prevent passing it)
|
||||||
if (req.query.client_secret) {
|
if (req.query.client_secret) {
|
||||||
return response.error(req, res, new error.InvalidRequest('client_secret field should not be passed to the authorization endpoint'), redirectUri)
|
throw new error.InvalidRequest('client_secret field should not be passed to the authorization endpoint')
|
||||||
}
|
}
|
||||||
|
|
||||||
clientId = req.query.client_id
|
clientId = req.query.client_id
|
||||||
console.debug('Parameter client_id is', clientId)
|
console.debug('Parameter client_id is', clientId)
|
||||||
|
|
||||||
if (!req.query.response_type) {
|
if (!req.query.response_type) {
|
||||||
return response.error(req, res, new error.InvalidRequest('response_type field is mandatory for authorization endpoint'), redirectUri)
|
throw new error.InvalidRequest('response_type field is mandatory for authorization endpoint')
|
||||||
}
|
}
|
||||||
|
|
||||||
responseType = req.query.response_type
|
responseType = req.query.response_type
|
||||||
console.debug('Parameter response_type is', responseType)
|
console.debug('Parameter response_type is', responseType)
|
||||||
|
|
||||||
switch (responseType) {
|
// Support multiple types
|
||||||
case 'code':
|
const responseTypes = responseType.split(' ')
|
||||||
grantType = 'authorization_code'
|
for (const i in responseTypes) {
|
||||||
break
|
switch (responseTypes[i]) {
|
||||||
case 'token':
|
case 'code':
|
||||||
grantType = 'implicit'
|
grantTypes.push('authorization_code')
|
||||||
break
|
break
|
||||||
default:
|
case 'token':
|
||||||
return response.error(req, res, new error.UnsupportedResponseType('Unknown response_type parameter passed'), redirectUri)
|
grantTypes.push('implicit')
|
||||||
|
break
|
||||||
|
// case 'id_token':
|
||||||
|
case 'none':
|
||||||
|
grantTypes.push(responseTypes[i])
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new error.UnsupportedResponseType('Unknown response_type parameter passed')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
console.debug('Parameter response_type is', responseType)
|
|
||||||
|
// Filter out duplicates
|
||||||
|
grantTypes = grantTypes.filter(function (value, index, self) {
|
||||||
|
return self.indexOf(value) === index
|
||||||
|
})
|
||||||
|
|
||||||
|
// "None" type cannot be combined with others
|
||||||
|
if (grantTypes.length > 1 && grantTypes.indexOf('none') !== -1) {
|
||||||
|
throw new error.InvalidRequest('Grant type "none" cannot be combined with other grant types')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug('Parameter grant_type is', grantTypes.join(' '))
|
||||||
|
|
||||||
const client = await req.oauth2.model.client.fetchById(clientId)
|
const client = await req.oauth2.model.client.fetchById(clientId)
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return response.error(req, res, new error.InvalidClient('Client not found'), redirectUri)
|
throw new error.InvalidClient('Client not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: multiple redirect URI
|
||||||
if (!req.oauth2.model.client.getRedirectUri(client)) {
|
if (!req.oauth2.model.client.getRedirectUri(client)) {
|
||||||
return response.error(req, res, new error.UnsupportedResponseType('The client has not set a redirect uri'), redirectUri)
|
throw new error.UnsupportedResponseType('The client has not set a redirect uri')
|
||||||
} else if (!req.oauth2.model.client.checkRedirectUri(client, redirectUri)) {
|
} else if (!req.oauth2.model.client.checkRedirectUri(client, redirectUri)) {
|
||||||
return response.error(req, res, new error.InvalidRequest('Wrong RedirectUri provided'), redirectUri)
|
throw new error.InvalidRequest('Wrong RedirectUri provided')
|
||||||
} else {
|
|
||||||
console.debug('redirect_uri check passed')
|
|
||||||
}
|
}
|
||||||
|
console.debug('redirect_uri check passed')
|
||||||
|
|
||||||
if (!req.oauth2.model.client.checkGrantType(client, grantType)) {
|
// The client needs to support all grant types
|
||||||
return response.error(req, res, new error.UnauthorizedClient('This client does not support this grant type'), redirectUri)
|
for (const i in grantTypes) {
|
||||||
} else {
|
if (!req.oauth2.model.client.checkGrantType(client, grantTypes[i]) && grantTypes[i] !== 'none') {
|
||||||
console.debug('Grant type check passed')
|
throw new error.UnauthorizedClient('This client does not support grant type ' + grantTypes[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
console.debug('Grant type check passed')
|
||||||
|
|
||||||
scope = req.oauth2.model.client.transformScope(req.query.scope)
|
scope = req.oauth2.model.client.transformScope(req.query.scope)
|
||||||
scope = req.oauth2.model.client.checkScope(client, scope)
|
scope = req.oauth2.model.client.checkScope(client, scope)
|
||||||
if (!scope) {
|
if (!scope) {
|
||||||
return response.error(req, res, new error.InvalidScope('Client does not allow access to this scope'), redirectUri)
|
throw new error.InvalidScope('Client does not allow access to this scope')
|
||||||
} else {
|
|
||||||
console.debug('Scope check passed')
|
|
||||||
}
|
}
|
||||||
|
console.debug('Scope check passed')
|
||||||
|
|
||||||
user = await req.oauth2.model.user.fetchFromRequest(req)
|
user = await req.oauth2.model.user.fetchFromRequest(req)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return response.error(req, res, new error.InvalidRequest('There is no currently logged in user'), redirectUri)
|
throw new error.InvalidRequest('There is no currently logged in user')
|
||||||
} else {
|
} else {
|
||||||
if (!user.username) {
|
if (!user.username) {
|
||||||
return response.error(req, res, new error.Forbidden(user), redirectUri)
|
throw new error.Forbidden(user)
|
||||||
}
|
}
|
||||||
console.debug('User fetched from request')
|
console.debug('User fetched from request')
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = null
|
let resObj = {}
|
||||||
|
let consented = false
|
||||||
|
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
let hasAuthorizedAlready = await usermodel.clientAllowed(user.id, client.id, scope)
|
|
||||||
if (client.verified === 1) {
|
if (client.verified === 1) {
|
||||||
hasAuthorizedAlready = true
|
consented = true
|
||||||
|
} else {
|
||||||
|
consented = await model.user.consented(user.id, client.id, scope)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hasAuthorizedAlready) {
|
// Ask for consent
|
||||||
if (grantType === 'authorization_code') {
|
if (!consented) return req.oauth2.decision(req, res, client, scope, user, redirectUri)
|
||||||
try {
|
|
||||||
data = await authorization.Code(req, res, client, scope, user, redirectUri, false)
|
|
||||||
} catch (err) {
|
|
||||||
return response.error(req, res, err, redirectUri)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.data(req, res, { code: data }, redirectUri)
|
for (const i in grantTypes) {
|
||||||
} else if (grantType === 'implicit') {
|
let data = null
|
||||||
try {
|
switch (grantTypes[i]) {
|
||||||
data = await authorization.Implicit(req, res, client, scope, user, redirectUri, false)
|
case 'authorization_code':
|
||||||
} catch (err) {
|
data = await authorization.Code(req, res, client, scope, user, redirectUri, !consented)
|
||||||
return response.error(req, res, err, redirectUri)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.data(req, res, {
|
resObj = Object.assign({ code: data }, resObj)
|
||||||
|
|
||||||
|
break
|
||||||
|
case 'implicit':
|
||||||
|
data = await authorization.Implicit(req, res, client, scope, user, redirectUri, !consented)
|
||||||
|
|
||||||
|
resObj = Object.assign({
|
||||||
token_type: 'bearer',
|
token_type: 'bearer',
|
||||||
access_token: data,
|
access_token: data,
|
||||||
expires_in: req.oauth2.model.accessToken.ttl
|
expires_in: req.oauth2.model.accessToken.ttl
|
||||||
}, redirectUri)
|
}, resObj)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return req.oauth2.decision(req, res, client, scope, user, redirectUri)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.error(req, res, new error.InvalidRequest('Invalid request method'), redirectUri)
|
break
|
||||||
|
case 'none':
|
||||||
|
resObj = {}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new error.UnsupportedResponseType('Unknown response_type parameter passed')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (grantType === 'authorization_code') {
|
// Return non-code response types as fragment instead of query
|
||||||
try {
|
return response.data(req, res, resObj, redirectUri, responseType !== 'code')
|
||||||
data = await authorization.Code(req, res, client, scope, user, redirectUri, true)
|
}, true)
|
||||||
} catch (err) {
|
|
||||||
return response.error(req, res, err, redirectUri)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.data(req, res, { code: data }, redirectUri)
|
|
||||||
} else if (grantType === 'implicit') {
|
|
||||||
try {
|
|
||||||
data = await authorization.Implicit(req, res, client, scope, user, redirectUri, true)
|
|
||||||
} catch (err) {
|
|
||||||
return response.error(req, res, err, redirectUri)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.data(req, res, {
|
|
||||||
token_type: 'bearer',
|
|
||||||
access_token: data,
|
|
||||||
expires_in: req.oauth2.model.accessToken.ttl
|
|
||||||
}, redirectUri)
|
|
||||||
} else {
|
|
||||||
return response.error(req, res, new error.InvalidRequest('Invalid request method'), redirectUri)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
@ -1,23 +1,22 @@
|
|||||||
import error from '../../error'
|
import error from '../../error'
|
||||||
import model from '../../model'
|
import model from '../../model'
|
||||||
|
|
||||||
module.exports = async (req, res, client, scope, user, redirectUri, createAllowFuture) => {
|
module.exports = async (req, res, client, scope, user, redirectUri, consentRequested) => {
|
||||||
let codeValue = null
|
let codeValue = null
|
||||||
|
|
||||||
if (req.method === 'POST' && req.session.csrf && !(req.body.csrf && req.body.csrf === req.session.csrf)) {
|
if (req.method === 'POST' && req.session.csrf && !(req.body.csrf && req.body.csrf === req.session.csrf)) {
|
||||||
throw new error.InvalidRequest('Invalid session')
|
throw new error.InvalidRequest('Invalid session')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (createAllowFuture) {
|
if (consentRequested) {
|
||||||
if (!req.body || (typeof req.body.decision) === 'undefined') {
|
if (!req.body || (typeof req.body.decision) === 'undefined') {
|
||||||
throw new error.InvalidRequest('No decision parameter passed')
|
throw new error.InvalidRequest('No decision parameter passed')
|
||||||
} else if (req.body.decision === '0') {
|
} else if (req.body.decision === '0') {
|
||||||
throw new error.AccessDenied('User denied access to the resource')
|
throw new error.AccessDenied('User denied access to the resource')
|
||||||
} else {
|
|
||||||
console.debug('Decision check passed')
|
|
||||||
}
|
}
|
||||||
|
console.debug('Decision check passed')
|
||||||
|
|
||||||
await model.user.allowClient(user.id, client.id, scope)
|
await model.user.consent(user.id, client.id, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1,23 +1,22 @@
|
|||||||
import error from '../../error'
|
import error from '../../error'
|
||||||
import model from '../../model'
|
import model from '../../model'
|
||||||
|
|
||||||
module.exports = async (req, res, client, scope, user, redirectUri, createAllowFuture) => {
|
module.exports = async (req, res, client, scope, user, redirectUri, consentRequested) => {
|
||||||
let accessTokenValue = null
|
let accessTokenValue = null
|
||||||
|
|
||||||
if (req.method === 'POST' && req.session.csrf && !(req.body.csrf && req.body.csrf === req.session.csrf)) {
|
if (req.method === 'POST' && req.session.csrf && !(req.body.csrf && req.body.csrf === req.session.csrf)) {
|
||||||
throw new error.InvalidRequest('Invalid session')
|
throw new error.InvalidRequest('Invalid session')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (createAllowFuture) {
|
if (consentRequested) {
|
||||||
if (!req.body || (typeof req.body.decision) === 'undefined') {
|
if (!req.body || (typeof req.body.decision) === 'undefined') {
|
||||||
throw new error.InvalidRequest('No decision parameter passed')
|
throw new error.InvalidRequest('No decision parameter passed')
|
||||||
} else if (req.body.decision === '0') {
|
} else if (req.body.decision === '0') {
|
||||||
throw new error.AccessDenied('User denied access to the resource')
|
throw new error.AccessDenied('User denied access to the resource')
|
||||||
} else {
|
|
||||||
console.debug('Decision check passed')
|
|
||||||
}
|
}
|
||||||
|
console.debug('Decision check passed')
|
||||||
|
|
||||||
await model.user.allowClient(user.id, client.id, scope)
|
await model.user.consent(user.id, client.id, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import error from '../error'
|
import error from '../error'
|
||||||
import response from '../response'
|
import response from '../response'
|
||||||
import wrap from '../../../../scripts/asyncRoute'
|
import wrap from '../wrap'
|
||||||
|
|
||||||
module.exports = wrap(async function (req, res) {
|
module.exports = wrap(async function (req, res) {
|
||||||
let clientId = null
|
let clientId = null
|
||||||
@ -12,21 +12,21 @@ module.exports = wrap(async function (req, res) {
|
|||||||
console.debug('Client credentials parsed from body parameters ', clientId, clientSecret)
|
console.debug('Client credentials parsed from body parameters ', clientId, clientSecret)
|
||||||
} else {
|
} else {
|
||||||
if (!req.headers || !req.headers.authorization) {
|
if (!req.headers || !req.headers.authorization) {
|
||||||
return response.error(req, res, new error.InvalidRequest('No authorization header passed'))
|
throw new error.InvalidRequest('No authorization header passed')
|
||||||
}
|
}
|
||||||
|
|
||||||
let pieces = req.headers.authorization.split(' ', 2)
|
let pieces = req.headers.authorization.split(' ', 2)
|
||||||
if (!pieces || pieces.length !== 2) {
|
if (!pieces || pieces.length !== 2) {
|
||||||
return response.error(req, res, new error.InvalidRequest('Authorization header is corrupted'))
|
throw new error.InvalidRequest('Authorization header is corrupted')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pieces[0] !== 'Basic') {
|
if (pieces[0] !== 'Basic') {
|
||||||
return response.error(req, res, new error.InvalidRequest('Unsupported authorization method:', pieces[0]))
|
throw new error.InvalidRequest('Unsupported authorization method:', pieces[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
pieces = Buffer.from(pieces[1], 'base64').toString('ascii').split(':', 2)
|
pieces = Buffer.from(pieces[1], 'base64').toString('ascii').split(':', 2)
|
||||||
if (!pieces || pieces.length !== 2) {
|
if (!pieces || pieces.length !== 2) {
|
||||||
return response.error(req, res, new error.InvalidRequest('Authorization header has corrupted data'))
|
throw new error.InvalidRequest('Authorization header has corrupted data')
|
||||||
}
|
}
|
||||||
|
|
||||||
clientId = pieces[0]
|
clientId = pieces[0]
|
||||||
@ -35,12 +35,12 @@ module.exports = wrap(async function (req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!req.body.token) {
|
if (!req.body.token) {
|
||||||
return response.error(req, res, new error.InvalidRequest('Token not provided in request body'))
|
throw new error.InvalidRequest('Token not provided in request body')
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = await req.oauth2.model.accessToken.fetchByToken(req.body.token)
|
const token = await req.oauth2.model.accessToken.fetchByToken(req.body.token)
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return response.error(req, res, new error.InvalidRequest('Token does not exist'))
|
throw new error.InvalidRequest('Token does not exist')
|
||||||
}
|
}
|
||||||
|
|
||||||
const ttl = req.oauth2.model.accessToken.getTTL(token)
|
const ttl = req.oauth2.model.accessToken.getTTL(token)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import token from './tokens'
|
import token from './tokens'
|
||||||
import error from '../error'
|
import error from '../error'
|
||||||
import response from '../response'
|
import response from '../response'
|
||||||
import wrap from '../../../../scripts/asyncRoute'
|
import wrap from '../wrap'
|
||||||
|
|
||||||
module.exports = wrap(async (req, res) => {
|
module.exports = wrap(async (req, res) => {
|
||||||
let clientId = null
|
let clientId = null
|
||||||
@ -14,21 +14,21 @@ module.exports = wrap(async (req, res) => {
|
|||||||
console.debug('Client credentials parsed from body parameters', clientId, clientSecret)
|
console.debug('Client credentials parsed from body parameters', clientId, clientSecret)
|
||||||
} else {
|
} else {
|
||||||
if (!req.headers || !req.headers.authorization) {
|
if (!req.headers || !req.headers.authorization) {
|
||||||
return response.error(req, res, new error.InvalidRequest('No authorization header passed'))
|
throw new error.InvalidRequest('No authorization header passed')
|
||||||
}
|
}
|
||||||
|
|
||||||
let pieces = req.headers.authorization.split(' ', 2)
|
let pieces = req.headers.authorization.split(' ', 2)
|
||||||
if (!pieces || pieces.length !== 2) {
|
if (!pieces || pieces.length !== 2) {
|
||||||
return response.error(req, res, new error.InvalidRequest('Authorization header is corrupted'))
|
throw new error.InvalidRequest('Authorization header is corrupted')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pieces[0] !== 'Basic') {
|
if (pieces[0] !== 'Basic') {
|
||||||
return response.error(req, res, new error.InvalidRequest('Unsupported authorization method:', pieces[0]))
|
throw new error.InvalidRequest('Unsupported authorization method:', pieces[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
pieces = Buffer.from(pieces[1], 'base64').toString('ascii').split(':', 2)
|
pieces = Buffer.from(pieces[1], 'base64').toString('ascii').split(':', 2)
|
||||||
if (!pieces || pieces.length !== 2) {
|
if (!pieces || pieces.length !== 2) {
|
||||||
return response.error(req, res, new error.InvalidRequest('Authorization header has corrupted data'))
|
throw new error.InvalidRequest('Authorization header has corrupted data')
|
||||||
}
|
}
|
||||||
|
|
||||||
clientId = pieces[0]
|
clientId = pieces[0]
|
||||||
@ -37,7 +37,7 @@ module.exports = wrap(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!req.body.grant_type) {
|
if (!req.body.grant_type) {
|
||||||
return response.error(req, res, new error.InvalidRequest('Request body does not contain grant_type parameter'))
|
throw new error.InvalidRequest('Request body does not contain grant_type parameter')
|
||||||
}
|
}
|
||||||
|
|
||||||
grantType = req.body.grant_type
|
grantType = req.body.grant_type
|
||||||
@ -46,16 +46,16 @@ module.exports = wrap(async (req, res) => {
|
|||||||
const client = await req.oauth2.model.client.fetchById(clientId)
|
const client = await req.oauth2.model.client.fetchById(clientId)
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return response.error(req, res, new error.InvalidClient('Client not found'))
|
throw new error.InvalidClient('Client not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
const valid = req.oauth2.model.client.checkSecret(client, clientSecret)
|
const valid = req.oauth2.model.client.checkSecret(client, clientSecret)
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return response.error(req, res, new error.UnauthorizedClient('Invalid client secret'))
|
throw new error.UnauthorizedClient('Invalid client secret')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!req.oauth2.model.client.checkGrantType(client, grantType) && grantType !== 'refresh_token') {
|
if (!req.oauth2.model.client.checkGrantType(client, grantType) && grantType !== 'refresh_token') {
|
||||||
return response.error(req, res, new error.UnauthorizedClient('Invalid grant type for the client'))
|
throw new error.UnauthorizedClient('Invalid grant type for the client')
|
||||||
} else {
|
} else {
|
||||||
console.debug('Grant type check passed')
|
console.debug('Grant type check passed')
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,10 @@ module.exports = async (oauth2, client, wantScope) => {
|
|||||||
|
|
||||||
if (!scope) {
|
if (!scope) {
|
||||||
throw new error.InvalidScope('Client does not allow access to this scope')
|
throw new error.InvalidScope('Client does not allow access to this scope')
|
||||||
} else {
|
|
||||||
console.debug('Scope check passed ', scope)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.debug('Scope check passed ', scope)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
resObj.access_token = await oauth2.model.accessToken.create(null, oauth2.model.client.getId(client), scope, oauth2.model.accessToken.ttl)
|
resObj.access_token = await oauth2.model.accessToken.create(null, oauth2.model.client.getId(client), scope, oauth2.model.accessToken.ttl)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import response from './response'
|
|
||||||
import error from './error'
|
import error from './error'
|
||||||
import wrap from '../../../scripts/asyncRoute'
|
import wrap from './wrap'
|
||||||
|
|
||||||
const middleware = wrap(async function (req, res, next) {
|
const middleware = wrap(async function (req, res, next) {
|
||||||
console.debug('Parsing bearer token')
|
console.debug('Parsing bearer token')
|
||||||
@ -12,12 +11,12 @@ const middleware = wrap(async function (req, res, next) {
|
|||||||
|
|
||||||
// Check authorization header
|
// Check authorization header
|
||||||
if (!pieces || pieces.length !== 2) {
|
if (!pieces || pieces.length !== 2) {
|
||||||
return response.error(req, res, new error.AccessDenied('Wrong authorization header'))
|
throw new error.AccessDenied('Wrong authorization header')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only bearer auth is supported
|
// Only bearer auth is supported
|
||||||
if (pieces[0].toLowerCase() !== 'bearer') {
|
if (pieces[0].toLowerCase() !== 'bearer') {
|
||||||
return response.error(req, res, new error.AccessDenied('Unsupported authorization method in header'))
|
throw new error.AccessDenied('Unsupported authorization method in header')
|
||||||
}
|
}
|
||||||
|
|
||||||
token = pieces[1]
|
token = pieces[1]
|
||||||
@ -29,15 +28,15 @@ const middleware = wrap(async function (req, res, next) {
|
|||||||
token = req.body.access_token
|
token = req.body.access_token
|
||||||
console.debug('Bearer token parsed from body params:', token)
|
console.debug('Bearer token parsed from body params:', token)
|
||||||
} else {
|
} else {
|
||||||
return response.error(req, res, new error.AccessDenied('Bearer token not found'))
|
throw new error.AccessDenied('Bearer token not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to fetch access token
|
// Try to fetch access token
|
||||||
const object = await req.oauth2.model.accessToken.fetchByToken(token)
|
const object = await req.oauth2.model.accessToken.fetchByToken(token)
|
||||||
if (!object) {
|
if (!object) {
|
||||||
response.error(req, res, new error.Forbidden('Token not found or has expired'))
|
throw new error.Forbidden('Token not found or has expired')
|
||||||
} else if (!req.oauth2.model.accessToken.checkTTL(object)) {
|
} else if (!req.oauth2.model.accessToken.checkTTL(object)) {
|
||||||
response.error(req, res, new error.Forbidden('Token is expired'))
|
throw new error.Forbidden('Token is expired')
|
||||||
} else {
|
} else {
|
||||||
req.oauth2.accessToken = object
|
req.oauth2.accessToken = object
|
||||||
console.debug('AccessToken fetched', object)
|
console.debug('AccessToken fetched', object)
|
||||||
|
@ -4,6 +4,11 @@ import Users from '../index'
|
|||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
|
|
||||||
const OAuthDB = {
|
const OAuthDB = {
|
||||||
|
scopes: {
|
||||||
|
email: ['See your Email address', 'View the user\'s email address'],
|
||||||
|
image: ['', 'View the user\'s profile picture'],
|
||||||
|
privilege: ['', 'See the user\'s privilege level']
|
||||||
|
},
|
||||||
accessToken: {
|
accessToken: {
|
||||||
ttl: config.oauth2.access_token_life,
|
ttl: config.oauth2.access_token_life,
|
||||||
getToken: (object) => {
|
getToken: (object) => {
|
||||||
@ -237,7 +242,7 @@ const OAuthDB = {
|
|||||||
|
|
||||||
return req.session.user
|
return req.session.user
|
||||||
},
|
},
|
||||||
clientAllowed: async (userId, clientId, scope) => {
|
consented: async (userId, clientId, scope) => {
|
||||||
if (typeof scope === 'object') {
|
if (typeof scope === 'object') {
|
||||||
scope = scope.join(' ')
|
scope = scope.join(' ')
|
||||||
}
|
}
|
||||||
@ -265,7 +270,7 @@ const OAuthDB = {
|
|||||||
|
|
||||||
return correct
|
return correct
|
||||||
},
|
},
|
||||||
allowClient: async (userId, clientId, scope) => {
|
consent: async (userId, clientId, scope) => {
|
||||||
if (!config.oauth2.save_decision) return true
|
if (!config.oauth2.save_decision) return true
|
||||||
if (typeof scope === 'object') {
|
if (typeof scope === 'object') {
|
||||||
scope = scope.join(' ')
|
scope = scope.join(' ')
|
||||||
|
@ -40,9 +40,9 @@ module.exports.error = function (req, res, err, redirectUri) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.data = function (req, res, obj, redirectUri, anchor) {
|
module.exports.data = function (req, res, obj, redirectUri, fragment) {
|
||||||
if (redirectUri) {
|
if (redirectUri) {
|
||||||
if (anchor) {
|
if (fragment) {
|
||||||
redirectUri += '#'
|
redirectUri += '#'
|
||||||
} else {
|
} else {
|
||||||
redirectUri += (redirectUri.indexOf('?') === -1 ? '?' : '&')
|
redirectUri += (redirectUri.indexOf('?') === -1 ? '?' : '&')
|
||||||
|
9
server/api/oauth2/wrap.js
Normal file
9
server/api/oauth2/wrap.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import response from './response'
|
||||||
|
|
||||||
|
module.exports = function (fn, redir) {
|
||||||
|
return function (req, res, next) {
|
||||||
|
fn(req, res, next).catch(e => {
|
||||||
|
return response.error(req, res, e, redir ? req.query.redirect_uri : null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
103
static/image/icynet-icon-pleroma.svg
Normal file
103
static/image/icynet-icon-pleroma.svg
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="29.898235mm"
|
||||||
|
height="29.898235mm"
|
||||||
|
viewBox="0 0 29.898235 29.898235"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8">
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath877">
|
||||||
|
<rect
|
||||||
|
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#6fefff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||||
|
id="rect879"
|
||||||
|
width="24.856066"
|
||||||
|
height="24.856066"
|
||||||
|
x="2.5211489"
|
||||||
|
y="2.5227857"
|
||||||
|
transform="rotate(2.4309033e-4)" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-1.2029979e-7,-0.00176382)">
|
||||||
|
<rect
|
||||||
|
transform="rotate(-3.5293181)"
|
||||||
|
y="3.4147503"
|
||||||
|
x="1.5723678"
|
||||||
|
height="24.856066"
|
||||||
|
width="24.856066"
|
||||||
|
id="rect1083"
|
||||||
|
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#00a1b5;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;font-variant-east_asian:normal" />
|
||||||
|
<rect
|
||||||
|
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#00e1fd;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;font-variant-east_asian:normal"
|
||||||
|
id="rect815"
|
||||||
|
width="24.856066"
|
||||||
|
height="24.856066"
|
||||||
|
x="0.13787289"
|
||||||
|
y="4.5749965"
|
||||||
|
transform="rotate(-8.5307657)" />
|
||||||
|
<rect
|
||||||
|
transform="rotate(2.4309033e-4)"
|
||||||
|
y="2.5227857"
|
||||||
|
x="2.5211489"
|
||||||
|
height="24.856066"
|
||||||
|
width="24.856066"
|
||||||
|
id="rect817"
|
||||||
|
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#6fefff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||||
|
<g
|
||||||
|
transform="matrix(0.06114614,0,0,0.06114614,-0.73486692,-0.70252952)"
|
||||||
|
id="g4612"
|
||||||
|
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#03a9f4;fill-opacity:1;stroke:none;stroke-width:4.88991;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1">
|
||||||
|
<path
|
||||||
|
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#03a9f4;fill-opacity:1;stroke:none;stroke-width:4.88991;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
|
||||||
|
d="M 235,89 V 423 H 152 V 115 l 26,-26 z"
|
||||||
|
id="path4495" />
|
||||||
|
<circle
|
||||||
|
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#03a9f4;fill-opacity:1;stroke:none;stroke-width:4.88991;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
|
||||||
|
id="path4497"
|
||||||
|
cy="115"
|
||||||
|
cx="178"
|
||||||
|
r="26" />
|
||||||
|
<circle
|
||||||
|
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#03a9f4;fill-opacity:1;stroke:none;stroke-width:4.88991;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
|
||||||
|
id="path4497-0"
|
||||||
|
cy="230"
|
||||||
|
cx="335"
|
||||||
|
r="26" />
|
||||||
|
<path
|
||||||
|
style="font-variation-settings:normal;vector-effect:none;fill:#03a9f4;fill-opacity:1;stroke:none;stroke-width:4.88991;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
|
||||||
|
d="M 277,256 V 89 l 84,3e-6 L 361.00002,230 335,256 Z"
|
||||||
|
id="path4516" />
|
||||||
|
<circle
|
||||||
|
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#03a9f4;fill-opacity:1;stroke:none;stroke-width:4.88991;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
|
||||||
|
id="path4497-0-6"
|
||||||
|
cy="397"
|
||||||
|
cx="335"
|
||||||
|
r="26" />
|
||||||
|
<path
|
||||||
|
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#03a9f4;fill-opacity:1;stroke:none;stroke-width:4.88991;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"
|
||||||
|
d="m 277,423 v -83 h 84 l 2e-5,57 L 335,423 Z"
|
||||||
|
id="path4516-5" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 6.6 KiB |
@ -54,7 +54,7 @@ block body
|
|||||||
i.fas.fa-fw.fa-lock
|
i.fas.fa-fw.fa-lock
|
||||||
|See your Password
|
|See your Password
|
||||||
li
|
li
|
||||||
i.fas.fa-fw.fa-gears
|
i.fas.fa-fw.fa-cogs
|
||||||
|Change your Account Settings
|
|Change your Account Settings
|
||||||
.alert.alert-info
|
.alert.alert-info
|
||||||
b Note!
|
b Note!
|
||||||
|
Reference in New Issue
Block a user