Skip to content

Commit

Permalink
Added tokenclaims idp_adapter (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
rorylshanks authored Mar 3, 2024
1 parent 6487e02 commit fe7d049
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 55 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ test-e2e:
docker compose -f docker-compose-test.yaml up -d
@echo "Waiting for start..." && sleep 5
@cd ./test/e2e && PUPPETEER_DISABLE_HEADLESS_WARNING=true npm run test || (docker compose -f ../../docker-compose-test.yaml logs vftest && exit 1)
# @docker compose -f docker-compose-test.yaml logs | grep -i error
docker compose -f docker-compose-test.yaml down
4 changes: 2 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@

import { reloadConfig } from './util/config.js';
import authz from './lib/authz.js';
import idp from './lib/idp.js';
import log from './util/logging.js'

async function main() {
log.info("Starting Verflow Server")
await reloadConfig()
await authz.scheduleUpdate()
await idp.scheduleUpdate()
}

main()
Expand Down
4 changes: 1 addition & 3 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
#!/bin/bash
docker buildx create --use --name multi --platform linux/arm64,linux/amd64
docker buildx build \
--platform linux/arm64,linux/amd64 \
-t rorylshanks/veriflow:latest \
-t rorylshanks/veriflow:debug \
--push \
-f Dockerfile \
.
docker buildx rm multi

1 change: 1 addition & 0 deletions example-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ cookie_secret: "ThisIsAFakeCookieSecret"
cookie_settings:
sameSite: "none"
secure: true
maxAge: 86400
redis_connection_string: redis://127.0.0.1:6379
idp_client_id: 00000000-1111-2222-3333-444444444444
idp_client_secret: "FAKEFAKEFAKEaingaigaeW9eic4ok3oojietheeFAKEFAKEFAKE"
Expand Down
49 changes: 2 additions & 47 deletions lib/authz.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,15 @@
import redisHelper from "../util/redis.js"
import Bossbat from 'bossbat';
import log from '../util/logging.js'
import { getConfig } from '../util/config.js'
import idp from './idp.js'
import { createJWT } from '../util/jwt.js';
import timestring from 'timestring';
import Cache from 'cache';
import fs from 'fs/promises'

let requestHeaderMapCache = new Cache(60 * 1000);

const redisClient = redisHelper.getClient()

const idpUpdater = new Bossbat({
connection: redisHelper.getRedisConfig(),
prefix: 'bossbat:',
ttl: timestring(getConfig().idp_refresh_directory_interval) * 1000
});

var currentConfig = getConfig()
let importedAdapter = await import(`./idp_adapters/${currentConfig.idp_provider}.js`)
let adapter = importedAdapter.default

async function update() {
try {
var startDate = Date.now()
await adapter.runUpdate()
var endDate = Date.now()
var duration = (endDate - startDate) / 1000
log.info(`Updated users from IDP in ${duration} seconds`)
} catch (error) {
log.error({error, details: error.message})
}
}

async function scheduleUpdate() {
let config = getConfig()
if (config.refresh_idp_at_start) {
update()
}
idpUpdater.hire('update-idp', {
every: getConfig().idp_refresh_directory_interval,
work: async () => {
try {
await update()
} catch (error) {
log.error({ message: "Failed up update users and groups from IDP", error })
}
},
});
}

async function authZRequest(req, res, route) {
var requestUrl = new URL(`${req.get("X-Forwarded-Proto")}://${req.get("X-Forwarded-Host")}${req.get("X-Forwarded-Path") || ""}`)
var userId = req.session.userId
var user = await adapter.getUserById(userId)
var user = await idp.getUserById(userId)
if (!user) {
log.info({ "action": "userDoesNotExistInIdp", "user": userId, context: { url: requestUrl } })
return false
Expand Down Expand Up @@ -158,6 +114,5 @@ async function getRequestHeaderMapConfig(user, route) {
}

export default {
scheduleUpdate,
authZRequest
}
64 changes: 64 additions & 0 deletions lib/idp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import redisHelper from "../util/redis.js"
import Bossbat from 'bossbat';
import log from '../util/logging.js'
import { getConfig } from '../util/config.js'
import timestring from 'timestring';

const idpUpdater = new Bossbat({
connection: redisHelper.getRedisConfig(),
prefix: 'bossbat:',
ttl: timestring(getConfig().idp_refresh_directory_interval) * 1000
});

var currentConfig = getConfig()
let importedAdapter = await import(`./idp_adapters/${currentConfig.idp_provider}.js`)
let adapter = importedAdapter.default

async function update() {
try {
var startDate = Date.now()
await adapter.runUpdate()
var endDate = Date.now()
var duration = (endDate - startDate) / 1000
log.info(`Updated users from IDP in ${duration} seconds`)
} catch (error) {
log.error({error, details: error.message})
}
}

async function scheduleUpdate() {
let config = getConfig()
if (config.refresh_idp_at_start) {
update()
}
idpUpdater.hire('update-idp', {
every: getConfig().idp_refresh_directory_interval,
work: async () => {
try {
await update()
} catch (error) {
log.error({ message: "Failed up update users and groups from IDP", error })
}
},
});
}

async function getUserById(userId) {
var user = await adapter.getUserById(userId)
return user
}

async function addNewUserFromClaims(userClaims) {
if (!adapter.addNewUserFromClaims) {
log.debug({ message: `Adapter ${currentConfig.idp_provider} does not support adding new users via claims, returning`, context: { claims: userClaims } })
return
}
log.debug({ message: "Attempting to add new user from claims", context: { claims: userClaims } })
await adapter.addNewUserFromClaims(userClaims)
}

export default {
getUserById,
scheduleUpdate,
addNewUserFromClaims
}
50 changes: 50 additions & 0 deletions lib/idp_adapters/tokenclaims.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import log from '../../util/logging.js'
import Cache from 'cache';
import redisHelper from '../../util/redis.js'
import { getConfig } from '../../util/config.js';

const redisClient = redisHelper.getClient()

let idpRedisResponse = new Cache(60 * 1000);

async function runUpdate() {
return true
}

async function getUserById(id) {
var idpResponse = idpRedisResponse.get(`veriflow:users:${id}`)
if (idpResponse) {
log.trace(`Returning IDP user ${id} from cache`)
return idpResponse
} else {
try {
log.debug("Cache miss, returning results from Redis")
var idpResponse = JSON.parse(await redisClient.get(`veriflow:users:${id}`))
idpRedisResponse.put(`veriflow:users:${id}`, idpResponse)
return idpResponse
} catch (error) {
log.error({ message: "Error getting user by ID", error: error.message })
return null
}
}
}

async function addNewUserFromClaims(claims) {
var currentConfig = getConfig()
var userId = claims[currentConfig.idp_provider_user_id_claim]

var userData = {
id: userId,
mail: claims.email,
...claims
};

await redisClient.set(`veriflow:users:${userId}`, JSON.stringify(userData))
await redisClient.expire(`veriflow:users:${userId}`, 87000); // expire in 24 hours
}

export default {
runUpdate,
getUserById,
addNewUserFromClaims
};
8 changes: 6 additions & 2 deletions lib/sso.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import dynamicBackend from './dynamic-backend.js'
import { checkAuthHeader } from './token-auth.js'
import crypto from 'crypto'
import errorpages from '../util/errorpage.js'
import idp from './idp.js'

function addParamsToUrl(baseUrl, params) {
const url = new URL(baseUrl);
Expand Down Expand Up @@ -262,15 +263,18 @@ async function verifySsoCallback(req, res) {


// var userInfo = await oauth_client.userinfo(callbackInfo.access_token)
var userIdClaim = callbackInfo.claims()[currentConfig.idp_provider_user_id_claim]
var userClaims = callbackInfo.claims()
var userIdClaim = userClaims[currentConfig.idp_provider_user_id_claim]

if (!userIdClaim) {
log.warn({ error: "User does not have a userId included in the ID token claims. Check setting idp_provider_user_id_claim", claims: callbackInfo.claims() })
log.warn({ error: "User does not have a userId included in the ID token claims. Check setting idp_provider_user_id_claim", claims: userClaims })
var html = await errorpages.renderErrorPage(500, "ERR_NO_USERID_IN_TOKEN")
res.status(500).send(html)
return
}

await idp.addNewUserFromClaims(userClaims)

req.session.loggedin = true;
req.session.userId = userIdClaim;

Expand Down
10 changes: 9 additions & 1 deletion util/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ function urlToCaddyUpstream(url) {
return `${toURL.hostname}:${toPort}`
}

function convertHeaderCase(str) {
return str
.split('-') // Split the string into an array of words by hyphen
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) // Capitalize the first letter of each word and make the rest lowercase
.join('-'); // Rejoin the words with hyphens
}

export default {
urlToCaddyUpstream
urlToCaddyUpstream,
convertHeaderCase
}

0 comments on commit fe7d049

Please sign in to comment.