Skip to content

Commit

Permalink
Merge branch 'travisghansen:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
autarchprinceps authored Nov 20, 2024
2 parents 90a9907 + c3337e0 commit f1b952d
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 35 deletions.
1 change: 1 addition & 0 deletions ASSERTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Valid options for method are:
- `contains-all` - Similar to `contains` but allows the `value` to be a list of
items. If **all** of the items in `value` are found in the `query` result then
the assertion passes. This assumes the `query` is returning a list of values.
- `empty` - The value is a truthy false, empty, or empty string.

## examples

Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# 0.13.3

Released 2023-10-25

- support `empty` assertion strategy
- support encoding headers with `uri` encoding
- migrate `state` to be stored server-side in more scenarios
- support assertions/headers based on additional authentication data using
decoded values of various tokens `id_token_decoded`, `access_token_decoded` and
`refresh_token_decoded`

# 0.13.2

Released 2023-06-27
Expand Down
2 changes: 1 addition & 1 deletion HEADERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ as tokens etc may not exist.
source: "userinfo",// userinfo, id_token, access_token, refresh_token, static, config_token, plugin_config, req, parentRequestInfo
query_engine: "jp",
query: "$.emails[*].email", // if left blank the data will be passed unaltered (ie: jwt encoded data)
encoding: "plain", // may be set to base64
encoding: "plain", // may be set to base64 or uri
query_engine: "jp",
query: "$.login",
Expand Down
10 changes: 9 additions & 1 deletion src/assertion/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ class Assertion {
let value = await this.query();
let test;

// prevent stupid during tests below
if (value === null || typeof value === "undefined") {
value = "";
}

logger.debug("asserting: %j against value: %j", this.config, value);

if (rule.case_insensitive) {
Expand Down Expand Up @@ -149,6 +154,9 @@ class Assertion {

test = rule.value.includes(value);
break;
case "empty":
test = value.length < 1 ? true : false;
break;
case "regex":
/**
* this splits the simple "/pattern/[flags]" syntaxt into something the
Expand Down Expand Up @@ -181,5 +189,5 @@ class Assertion {
}

module.exports = {
Assertion
Assertion,
};
4 changes: 4 additions & 0 deletions src/header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ class HeaderInjector {
case "base64":
value = base64_encode(value);
break;
case "uri":
case "url":
value = encodeURIComponent(value);
break;
default:
case "plain":
// noop
Expand Down
130 changes: 97 additions & 33 deletions src/plugin/oauth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const request = require("request");
const URI = require("uri-js");
const utils = require("../../utils");
const { v4: uuidv4 } = require("uuid");
const YAML = require("yaml");

custom.setHttpOptionsDefaults({
followRedirect: false,
Expand Down Expand Up @@ -975,7 +976,7 @@ class BaseOauthPlugin extends BasePlugin {
res.json({});
});

server.WebServer.get("/oauth/end-session-redirect", (req, res) => {
server.WebServer.get("/oauth/end-session-redirect", async (req, res) => {
server.logger.silly("%j", {
headers: req.headers,
body: req.body,
Expand All @@ -989,6 +990,22 @@ class BaseOauthPlugin extends BasePlugin {
"hex"
);
state = jwt.verify(state, issuer_sign_secret);
const state_id = state.state_id;
state = await STORE_HELPER.get(
"state",
STATE_CACHE_PREFIX + state_id
);

if (!state) {
throw new Error(`invalid state`);
}

STORE_HELPER.delete("state", STATE_CACHE_PREFIX + state_id).catch(
() => {
// do nothing
}
);

const redirect_uri = state.redirect_uri;
server.logger.verbose("state redirect uri: %j", redirect_uri);

Expand Down Expand Up @@ -1149,7 +1166,7 @@ class BaseOauthPlugin extends BasePlugin {
let ttl;
let bc_config;
if (process.env.EAS_BACKCHANNEL_LOGOUT_CONFIG) {
bc_config = JSON.parse(process.env.EAS_BACKCHANNEL_LOGOUT_CONFIG);
bc_config = YAML.parse(process.env.EAS_BACKCHANNEL_LOGOUT_CONFIG);
}

// checked forced values
Expand Down Expand Up @@ -1510,34 +1527,50 @@ class BaseOauthPlugin extends BasePlugin {
sessionPayload.tokenSet &&
sessionPayload.tokenSet.id_token
) {
let idToken = jwt.decode(sessionPayload.tokenSet.id_token);
//let idToken = jwt.decode(sessionPayload.tokenSet.id_token);
// TODO: this check may not be entirely needed/wanted
if (idToken.session_state) {
const payload = {
redirect_uri: redirect_uri,
aud: configAudMD5,
req: {
headers: {
referer: req.headers.referer,
},
// idToken.session_state

const state_id = uuidv4();
const statePointerPayload = {
state_id,
};

/**
* This process will have a registered redirect_uri with the OP that should point to
* /oauth/end-session-redirect
*
* upon arrival at that endpoint the state is retrieved to get the real redirect_uri
* otherwise state is unused
*/
const statePayload = {
redirect_uri: redirect_uri,
aud: configAudMD5,
req: {
headers: {
referer: req.headers.referer,
},
request_is_xhr,
};
const stateToken = jwt.sign(payload, issuer_sign_secret);
const state = plugin.server.utils.encrypt(
issuer_encrypt_secret,
stateToken,
"hex"
);
},
request_is_xhr,
};

redirect_uri = await client.endSessionUrl({
id_token_hint: sessionPayload.tokenSet.id_token,
post_logout_redirect_uri:
plugin.config.features.logout.end_provider_session
.post_logout_redirect_uri,
state,
});
}
// persist state
await plugin.save_state(state_id, statePayload);

const stateToken = jwt.sign(statePointerPayload, issuer_sign_secret);
const state = plugin.server.utils.encrypt(
issuer_encrypt_secret,
stateToken,
"hex"
);

redirect_uri = await client.endSessionUrl({
id_token_hint: sessionPayload.tokenSet.id_token,
post_logout_redirect_uri:
plugin.config.features.logout.end_provider_session
.post_logout_redirect_uri,
state,
});
}

plugin.server.logger.info("deleting session: %s", session_id);
Expand Down Expand Up @@ -1579,6 +1612,12 @@ class BaseOauthPlugin extends BasePlugin {

plugin.server.logger.verbose("decoded state: %j", state);

if (!state) {
plugin.server.logger.verbose("invalid state");
res.statusCode = 503;
return res;
}

const configAudMD5 = configToken.audMD5;
plugin.server.logger.verbose("audMD5: %s", configAudMD5);

Expand Down Expand Up @@ -2073,7 +2112,7 @@ class BaseOauthPlugin extends BasePlugin {
Date.now() / 1000 > sessionPayload.exp
) {
plugin.server.logger.verbose("session has expired");
store.del(SESSION_CACHE_PREFIX + session_id);
plugin.delete_session(session_id);
return respond_to_failed_authorization();
}

Expand Down Expand Up @@ -2216,6 +2255,11 @@ class BaseOauthPlugin extends BasePlugin {
}
}

await plugin.prepare_token_headers(res, sessionPayload);
await plugin.prepare_authentication_data(res, sessionPayload);

// TODO: run js assertions here with all authdata etc

let now = Date.now() / 1000;
if (
plugin.config.features.session_expiry !== true &&
Expand All @@ -2228,9 +2272,6 @@ class BaseOauthPlugin extends BasePlugin {
) {
await plugin.update_session(session_id, sessionPayload);
}

await plugin.prepare_token_headers(res, sessionPayload);
await plugin.prepare_authentication_data(res, sessionPayload);
res.statusCode = 200;
return res;
} else {
Expand All @@ -2255,7 +2296,7 @@ class BaseOauthPlugin extends BasePlugin {

let bc_config;
if (process.env.EAS_BACKCHANNEL_LOGOUT_CONFIG) {
bc_config = JSON.parse(process.env.EAS_BACKCHANNEL_LOGOUT_CONFIG);
bc_config = YAML.parse(process.env.EAS_BACKCHANNEL_LOGOUT_CONFIG);
}

// checked forced values
Expand Down Expand Up @@ -2565,6 +2606,22 @@ class BaseOauthPlugin extends BasePlugin {
}

async prepare_authentication_data(res, sessionData) {
let id_token_decoded;
let access_token_decoded;
let refresh_token_decoded;

try {
id_token_decoded = jwt.decode(sessionData.tokenSet.id_token);
} catch {}

try {
access_token_decoded = jwt.decode(sessionData.tokenSet.access_token);
} catch {}

try {
refresh_token_decoded = jwt.decode(sessionData.tokenSet.refresh_token);
} catch {}

res.setAuthenticationData({
userinfo:
sessionData.userinfo && sessionData.userinfo.data
Expand All @@ -2574,6 +2631,9 @@ class BaseOauthPlugin extends BasePlugin {
access_token: sessionData.tokenSet.access_token,
refresh_token: sessionData.tokenSet.refresh_token,
token_set: sessionData.tokenSet,
id_token_decoded,
access_token_decoded,
refresh_token_decoded,
});
}

Expand Down Expand Up @@ -2789,7 +2849,11 @@ class BaseOauthPlugin extends BasePlugin {
) {
plugin.server.logger.debug("verifying id_token signature");

let secret = _.get(plugin.config, "assertions.sig.secret", issuer.jwks_uri);
let secret = _.get(
plugin.config,
"assertions.sig.secret",
issuer.jwks_uri
);

try {
// resolves to decoded token, if fails will go into catch
Expand Down

0 comments on commit f1b952d

Please sign in to comment.