Skip to content
Snippets Groups Projects
Verified Commit 6d067dd7 authored by Louis's avatar Louis :fire:
Browse files

Add endpoints for managing oauth client redirects, fix model methods for refresh token flow

parent fa1f11e9
No related branches found
No related tags found
No related merge requests found
...@@ -19,6 +19,19 @@ class AccessToken extends BaseModel { ...@@ -19,6 +19,19 @@ class AccessToken extends BaseModel {
} }
} }
async toOAuthInterface() {
const client = await this.getOAuthClient()
const user = await this.getUser()
return {
accessToken: this.token,
accessTokenExpiresAt: this.expires_at,
scope: this.scope,
client: client.toOAuthInterface(),
user,
}
}
toJSON() { toJSON() {
const user = this.user ? {user: this.user} : {} const user = this.user ? {user: this.user} : {}
return { return {
......
...@@ -19,6 +19,19 @@ class RefreshToken extends BaseModel { ...@@ -19,6 +19,19 @@ class RefreshToken extends BaseModel {
} }
} }
async toOAuthInterface() {
const client = await this.getOAuthClient()
const user = await this.getUser()
return {
refreshToken: this.token,
refreshTokenExpiresAt: this.expires_at,
scope: this.scope,
client: client.toOAuthInterface(),
user,
}
}
toJSON() { toJSON() {
const user = this.user ? { user: this.user } : { } const user = this.user ? { user: this.user } : { }
return { return {
......
...@@ -109,13 +109,14 @@ class User extends BaseModel { ...@@ -109,13 +109,14 @@ class User extends BaseModel {
return await crypto.encrypt(JSON.stringify({ session: this.id })) return await crypto.encrypt(JSON.stringify({ session: this.id }))
} }
async asJWTToken() { async asJWTToken(extras = {}) {
const { sign } = require('core/utils/jwt') const { sign } = require('core/utils/jwt')
return await sign({ return await sign({
session: { session: {
id: this.id, id: this.id,
roles: ['overseer', 'user'], roles: ['overseer', 'user'],
}, },
...extras,
}) })
} }
......
...@@ -15,18 +15,10 @@ function createTokenPair(user, client, access, refresh = {}) { ...@@ -15,18 +15,10 @@ function createTokenPair(user, client, access, refresh = {}) {
} }
} }
async function createClientEncryptedToken(client, user, scope) { async function createClientEncryptedToken(client, userModel, scope) {
const payload = { const user = await User.findByPk(userModel.id)
client_id: client.id, const payload = user.asJWTToken({ cid: client.id, scope })
user_id: user.id, return payload
scope: scope,
}
if (client.secret == null) {
return crypto.encrypt(payload)
}
return await crypto.encryptWith(Buffer.from(client.secret, 'hex'), JSON.stringify(payload))
} }
const model = { const model = {
...@@ -50,8 +42,9 @@ const model = { ...@@ -50,8 +42,9 @@ const model = {
getAccessToken(token) { getAccessToken(token) {
return AccessToken.findOne({ include: [ { model: User }, { model: OAuthClient } ], where: { token: { [Op.eq]: token } } }) return AccessToken.findOne({ include: [ { model: User }, { model: OAuthClient } ], where: { token: { [Op.eq]: token } } })
}, },
getRefreshToken(token) { async getRefreshToken(token) {
return RefreshToken.findOne({ include: [ { model: User }, { model: OAuthClient } ], where: { token: { [Op.eq]: token } } }) const t = await RefreshToken.findOne({ include: [ { model: User }, { model: OAuthClient } ], where: { token: { [Op.eq]: token } } })
return await t?.toOAuthInterface()
}, },
getUser: async function getAuthUser(email, password) { getUser: async function getAuthUser(email, password) {
...@@ -188,6 +181,8 @@ class KoaOAuthServer { ...@@ -188,6 +181,8 @@ class KoaOAuthServer {
} }
} }
console.log(ctx.request.query, query)
const authState = await crypto.encrypt(JSON.stringify({ redirect: 'authorize', query: ctx.request.query })) const authState = await crypto.encrypt(JSON.stringify({ redirect: 'authorize', query: ctx.request.query }))
if (!user) { if (!user) {
return ctx.redirect(`/login?login_state=${ authState }`) return ctx.redirect(`/login?login_state=${ authState }`)
...@@ -259,7 +254,7 @@ const scopeDescriptionMap = { ...@@ -259,7 +254,7 @@ const scopeDescriptionMap = {
description: 'Full access to your account, including the ability to create, update and delete any user information, metrics and files.' description: 'Full access to your account, including the ability to create, update and delete any user information, metrics and files.'
} }
} }
function describeScopeRequest(scope) { function describeScopeRequest(scope = '*') {
const scopes = scope.split(' ') const scopes = scope.split(' ')
return scopes.map(s => scopeDescriptionMap[s]) return scopes.map(s => scopeDescriptionMap[s])
} }
......
...@@ -24,4 +24,55 @@ exports.listClients = async ctx => { ...@@ -24,4 +24,55 @@ exports.listClients = async ctx => {
ctx.body = { ctx.body = {
clients: clients.map(c => c.toOAuthInterface()) clients: clients.map(c => c.toOAuthInterface())
} }
}
exports.addClientRedirect = async ctx => {
const client = ctx.models?.oauthClient
const user = await ctx.services['core.auth'].getUser()
if (client?.owner_id !== user.id) {
throw new HttpError(403, 'You do not have permission to modify this oauth client')
}
const uri = ctx.request.body?.uri
if (!uri?.trim()) {
throw new HttpError(400, 'Must provide a URI to add to the client')
}
const uris = new Set(client.redirect_uris)
if (!uris.has(uri)) {
client.redirect_uris = [
...client.redirect_uris,
uri,
]
await client.save()
}
ctx.body = { client: client.toOAuthInterface() }
}
exports.removeClientRedirect = async ctx => {
const client = ctx.models?.oauthClient
const user = await ctx.services['core.auth'].getUser()
if (client.owner_id !== user.id) {
throw new HttpError(403, 'You do not have permission to modify this oauth client')
}
const uri = ctx.request.body?.uri
if (!uri?.trim()) {
throw new HttpError(400, 'Must provide a URI to add to the client')
}
const uris = new Set(client.redirect_uris)
if (uris.has(uri)) {
uris.delete(uri)
client.redirect_uris = Array.from(uris)
await client.save()
}
ctx.body = { client: client.toOAuthInterface() }
} }
\ No newline at end of file
...@@ -43,6 +43,18 @@ module.exports = async (ctx, next) => { ...@@ -43,6 +43,18 @@ module.exports = async (ctx, next) => {
} finally { } finally {
t.setName(`[${ ctx.method }] ${ ctx._matchedRouteName ?? ctx._matchedRoute ?? ctx.path }`) t.setName(`[${ ctx.method }] ${ ctx._matchedRouteName ?? ctx._matchedRoute ?? ctx.path }`)
t.setHttpStatus(ctx.status) t.setHttpStatus(ctx.status)
threadContext.stopTransaction() const user = ctx.services['core.auth']._user
Sentry.configureScope(scope => {
if (user) {
scope.setUser({
name: user.name,
email: user.email,
id: user.id,
ip_address: ctx.ip,
})
}
threadContext.stopTransaction()
})
} }
} }
\ No newline at end of file
const { OAuthClient } = require('database/models')
module.exports = async (id, ctx, next) => {
ctx.models = ctx.models ?? {}
ctx.models.oauthClient = await OAuthClient.findByPk(id)
return await next()
}
\ No newline at end of file
const controller = (name, method) => require(`./controllers/${ name }`)[method] const controller = (name, method) => require(`./controllers/${ name }`)[method]
const param = (name) => require(`./params/${ name }`)
const AuthServer = require('domain/auth/AuthServer') const AuthServer = require('domain/auth/AuthServer')
const { env, config } = require('bootstrap') const { env, config } = require('bootstrap')
...@@ -103,8 +105,12 @@ function mount(api) { ...@@ -103,8 +105,12 @@ function mount(api) {
api.post('/auth/reset-token', controller('api/auth', 'triggerPasswordReset')) api.post('/auth/reset-token', controller('api/auth', 'triggerPasswordReset'))
api.post('/auth/reset-password', controller('api/auth', 'handlePasswordReset')) api.post('/auth/reset-password', controller('api/auth', 'handlePasswordReset'))
api.param('oauthClientId', param('oauth_client'))
api.get('/oauth/clients', controller('api/oauth', 'listClients')) api.get('/oauth/clients', controller('api/oauth', 'listClients'))
api.post('/oauth/clients', controller('api/oauth', 'createClient')) api.post('/oauth/clients', controller('api/oauth', 'createClient'))
api.post('/oauth/clients/:oauthClientId/redirects', controller('api/oauth', 'addClientRedirect'))
api.delete('/oauth/clients/:oauthClientId/redirects', controller('api/oauth', 'removeClientRedirect'))
api.get('/self', controller('api/user', 'self')) api.get('/self', controller('api/user', 'self'))
api.get('/self/bundles', controller('api/app', 'getBundles')) api.get('/self/bundles', controller('api/app', 'getBundles'))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment