add userinfo endpoint

This commit is contained in:
bain 2024-09-13 23:07:46 +02:00
parent 278fc65b2a
commit e0b571a74a
Signed by: bain
GPG key ID: 31F0F25E3BED0B9B

View file

@ -404,23 +404,27 @@ async fn authenticate_endpoint(mut req: Request<AppState>) -> tide::Result {
}
}
// The token is random because there are no resources protected by the token anyway.
let mut access_token = [0u8; 32];
SystemRandom::new().fill(&mut access_token)?;
let access_token = base64_coder::URL_SAFE_NO_PAD.encode(&access_token);
let id_token = create_id_token(
req.state(),
&credentials.0,
&code_info.account,
code_info.nonce,
)?;
req.state()
.successful_logins
.fetch_add(1, Ordering::Relaxed);
// give access code
Ok(Response::builder(200).body(json!({
"access_token": access_token,
"token_type": "Bearer",
"expires_in": TOKEN_EXPIRATION,
"id_token": create_id_token(req.state(), &credentials.0, &code_info.account, code_info.nonce)?,
"scope": "openid profile email"
})).into())
Ok(Response::builder(200)
.body(json!({
"access_token": id_token,
"token_type": "Bearer",
"expires_in": TOKEN_EXPIRATION,
"id_token": id_token,
"scope": "openid profile email"
}))
.into())
}
async fn jwks_endpoint(req: Request<AppState>) -> tide::Result {
@ -445,6 +449,7 @@ async fn configuration_endpoint(req: Request<AppState>) -> tide::Result {
"issuer": uri,
"authorization_endpoint": uri.join("/authorize")?,
"token_endpoint": uri.join("/token")?,
"userinfo_endpoint": uri.join("/userinfo")?,
"jwks_uri": uri.join("/jwks")?,
"response_types_supported": ["code"],
"subject_types_supported": ["pairwise"],
@ -493,6 +498,60 @@ logins_success {}
.into())
}
/// We expect the ID token as our access token
async fn userinfo_endpoint(req: Request<AppState>) -> tide::Result {
// this is all wrapped, because if something fails, then its the
// client's fault anyways (if there aren't any bugs, oh well...)
let resp = || {
let jwt = req
.header("Authorization")
.and_then(|v| v.get(0).unwrap().as_str().strip_prefix("Bearer "))
.ok_or(anyhow!("no bearer token"))?; // extract token
let (message, signature) = jwt.rsplit_once(".").ok_or(anyhow!("bad token form"))?;
let pk: ring::rsa::PublicKeyComponents<Vec<u8>> = req.state().signing_key.public().into();
pk.verify(
&ring::signature::RSA_PKCS1_2048_8192_SHA256,
message.as_bytes(),
&base64_coder::URL_SAFE_NO_PAD.decode(signature)?,
)?;
let (_header, claims) = message.rsplit_once(".").ok_or(anyhow!("bad token form"))?;
let decoded_claims = base64_coder::URL_SAFE_NO_PAD.decode(claims)?;
let decoded_claims: serde_json::Value = serde_json::from_slice(&decoded_claims)?;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("no time travelling allowed before 1970")
.as_secs();
if decoded_claims["exp"]
.as_u64()
.ok_or(anyhow!("invalid exp"))?
< now
{
Err(anyhow!("token expired"))
} else {
Ok(Response::builder(200)
.content_type("application/json")
.body(decoded_claims)
.build())
}
};
#[allow(unused_variables)] // rustc complains when building in release mode
resp().or_else(|e| {
#[cfg(debug_assertions)]
log::debug!("userinfo error: {}", e);
Ok(Response::builder(401)
.header("WWW-Authenticate", "Bearer error=\"invalid_token\"")
.build())
})
}
pub struct AuthStore {
pub auths: HashMap<String, Authorization>,
pub expirations: LinkedList<(String, u64)>,
@ -563,6 +622,7 @@ async fn main() -> Result<()> {
.get(configuration_endpoint);
app.at("/new-account").get(create_account_endpoint);
app.at("/metrics").get(metrics_endpoint);
app.at("/userinfo").get(userinfo_endpoint);
auto_serve_dir!(app, "/static", "static");