-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OIDC stubbing #5
Changes from all commits
46a0f16
05d86a7
ccc4a45
16207ec
454577f
914a5c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { sessions } from '~/src/api/oidc/helpers/session-store' | ||
|
||
const oidcSessionController = { | ||
handler: async (request, h) => { | ||
return h.response(sessions).code(200) | ||
} | ||
} | ||
|
||
export { oidcSessionController } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { oidcConfig } from '~/src/api/oidc/oidc-config' | ||
import { validateScope } from '~/src/api/oidc/helpers/validate-scope' | ||
import { newSession } from '~/src/api/oidc/helpers/session-store' | ||
import { allUsers } from '~/src/api/oidc/helpers/users' | ||
import { renderLoginPage } from '~/src/api/oidc/helpers/render-login-page' | ||
|
||
const authorizeController = { | ||
handler: (request, h) => { | ||
// a bit of a hack, but basically if the user param hasn't been set | ||
// show a 'login' page where they can select which fake user they want | ||
if (request.query.user === undefined) { | ||
return renderLoginPage(request.url, h) | ||
} | ||
|
||
// TODO check these are all set, use joi or something | ||
const clientId = request.query.client_id | ||
const responseType = request.query.response_type | ||
const redirectUri = request.query.redirect_uri | ||
const state = request.query.state | ||
const scope = request.query.scope | ||
const codeChallengeMethod = request.query.code_challenge_method | ||
|
||
// validate request | ||
const unsupportedScopes = validateScope(scope) | ||
if (unsupportedScopes.length > 0) { | ||
return h | ||
.response(`Unsupported scopes ${unsupportedScopes.join(',')}`) | ||
.code(400) | ||
} | ||
|
||
if (clientId !== oidcConfig.clientId) { | ||
return h.response(`Invalid client id ${clientId}`).code(401) | ||
} | ||
|
||
if (!oidcConfig.responseTypesSupported.includes(responseType)) { | ||
return h.response(`Unsupported response type ${responseType}`).code(400) | ||
} | ||
|
||
if ( | ||
codeChallengeMethod && | ||
!oidcConfig.codeChallengeMethodsSupported.includes(codeChallengeMethod) | ||
) { | ||
return h | ||
.response(`Unsupported code_challenge_method ${codeChallengeMethod}`) | ||
.code(400) | ||
} | ||
|
||
const user = allUsers[request.query.user] | ||
if (user === undefined) { | ||
request.logger.error(`Invalid user selected ${request.query.user}`) | ||
return h.response(`Invalid user selection!`).code(400) | ||
} | ||
|
||
// create session | ||
const session = newSession( | ||
scope, | ||
request.query.nonce, | ||
user, | ||
request.query.code_challenge, | ||
request.query.code_challenge_method | ||
) | ||
|
||
const location = new URL(redirectUri) | ||
location.searchParams.append('code', session.sessionId) | ||
location.searchParams.append('state', state) | ||
return h.redirect(location.toString()) | ||
} | ||
} | ||
|
||
export { authorizeController } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { oidcConfig } from '~/src/api/oidc/oidc-config' | ||
import { | ||
getSessionByToken, | ||
sessions | ||
} from '~/src/api/oidc/helpers/session-store' | ||
import { | ||
generateIDToken, | ||
generateRefreshToken, | ||
generateToken | ||
} from '~/src/api/oidc/helpers/oidc-crypto' | ||
import { validateCodeChallenge } from '~/src/api/oidc/helpers/validate-code-challenge' | ||
|
||
const tokenController = { | ||
handler: (request, h) => { | ||
const logger = request.logger | ||
|
||
const clientId = request.payload.client_id | ||
const clientSecret = request.payload.client_secret | ||
const grantType = request.payload.grant_type | ||
const code = request.payload.code | ||
const refreshToken = request.payload.refresh_token | ||
const codeVerifier = request.payload.code_verifier | ||
|
||
// validate client id | ||
if (clientId !== oidcConfig.clientId) { | ||
logger.error(`Invalid client id ${clientId}`) | ||
return h.response(`Invalid client id ${clientId}`).code(401) | ||
} | ||
|
||
// validate secret | ||
if (clientSecret !== oidcConfig.clientSecret) { | ||
logger.error(`Invalid client secret`) | ||
return h.response(`Invalid client secret`).code(401) | ||
} | ||
|
||
// get the session depending on the grant type | ||
let result = null | ||
|
||
if (grantType === 'authorization_code') { | ||
logger.info('handling authorization code') | ||
result = getSessionForAuthorizationCode(code) | ||
const { valid, err } = validateCodeChallenge(result.session, codeVerifier) | ||
if (!valid) { | ||
logger.error(err) | ||
return h.response(err).code(401) | ||
} | ||
} else if (grantType === 'refresh_token') { | ||
logger.info('handling refresh token code') | ||
result = getSessionForRefreshToken(refreshToken) | ||
} else { | ||
return h.response(`invalid grant type ${grantType}`).code(400) | ||
} | ||
|
||
const { session, valid } = result | ||
|
||
if (!valid) { | ||
logger.error('session was missing or invalid') | ||
return h | ||
.response(`invalid code/token for grant type ${grantType}`) | ||
.code(400) | ||
} | ||
|
||
// build the token response | ||
|
||
const tokenResponse = { | ||
token_type: 'bearer', | ||
expires_in: oidcConfig.ttl | ||
} | ||
|
||
// copy over the refresh token if its not there | ||
// unsure if we still need the !== string 'null' check anymore | ||
if (refreshToken && refreshToken !== 'null') { | ||
logger.info(`refresh token ${refreshToken}`) | ||
tokenResponse.refresh_token = refreshToken | ||
} | ||
|
||
tokenResponse.access_token = generateToken(request.keys, session) | ||
|
||
if (session.scopes[0] === 'openid') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's correct. Similarly, you only generate a refresh token if scope |
||
tokenResponse.id_token = generateIDToken(request.keys, session) | ||
} | ||
|
||
if (session.scopes[0] === 'refresh') { | ||
logger.info('generating a refresh token') | ||
tokenResponse.refresh_token = generateRefreshToken(request.keys, session) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check my comment above on |
||
} | ||
|
||
logger.info(tokenResponse) | ||
|
||
return h | ||
.response(tokenResponse) | ||
.header('Cache-Control', 'no-cache, no-store, must-revalidate') | ||
.code(200) | ||
} | ||
} | ||
|
||
function getSessionForAuthorizationCode(code) { | ||
const session = sessions[code] | ||
if (!session || session.granted) { | ||
return { session: null, valid: false } | ||
} | ||
sessions[code].granted = true | ||
return { | ||
session, | ||
valid: true | ||
} | ||
} | ||
|
||
function getSessionForRefreshToken(refreshToken) { | ||
const session = getSessionByToken(refreshToken) | ||
return { | ||
session, | ||
valid: session !== undefined | ||
} | ||
} | ||
|
||
export { tokenController } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import jsonwebtoken from 'jsonwebtoken' | ||
|
||
const userInfoController = { | ||
handler: (request, h) => { | ||
const authHeader = request.headers.authorization | ||
if (authHeader === undefined || !authHeader.startsWith('Bearer ')) { | ||
return h.response('Missing bearer token').code(401) | ||
} | ||
// drop 'Bearer ' | ||
const token = authHeader.slice(7) | ||
|
||
try { | ||
const decoded = jsonwebtoken.verify(token, request.keys.pem.publicKey) | ||
return h.response(decoded).code(200) | ||
} catch (e) { | ||
request.logger.error(e) | ||
return h.response('invalid token').code(401) | ||
} | ||
} | ||
} | ||
|
||
export { userInfoController } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if that's actually needed cause you handle the refresh token further down?