|
@@ -1,8 +1,10 @@
|
|
|
-use crate::{schema, token};
|
|
|
+use crate::{key, schema, token, UIDCError};
|
|
|
use microrm::prelude::*;
|
|
|
use ring::signature::KeyPair;
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
+use super::session::SessionHelper;
|
|
|
+
|
|
|
type Request = tide::Request<super::ServerStateWrapper>;
|
|
|
|
|
|
#[derive(serde::Serialize)]
|
|
@@ -52,7 +54,7 @@ fn do_code_authorize(
|
|
|
request: Request,
|
|
|
qp: AuthorizeQueryParams,
|
|
|
state: Option<&str>,
|
|
|
- client: microrm::WithID<schema::Client>,
|
|
|
+ client: microrm::Stored<schema::Client>,
|
|
|
) -> Result<tide::Response, OIDCError> {
|
|
|
todo!()
|
|
|
}
|
|
@@ -61,26 +63,47 @@ fn do_token_authorize(
|
|
|
request: Request,
|
|
|
qp: AuthorizeQueryParams,
|
|
|
state: Option<&str>,
|
|
|
- client: microrm::WithID<schema::Client>,
|
|
|
+ client: microrm::Stored<schema::Client>,
|
|
|
) -> Result<tide::Response, OIDCError> {
|
|
|
- let qi = request.state().core.pool.query_interface();
|
|
|
-
|
|
|
- let shelper = super::session::SessionHelper::new(&request);
|
|
|
-
|
|
|
- let sauth = shelper
|
|
|
- .get_session(&request)
|
|
|
- .and_then(|sid| shelper.get_auth_for_session(shelper.get_realm().unwrap(), sid));
|
|
|
+ let shelper = SessionHelper::new(&request);
|
|
|
+ let realm = shelper.get_realm().map_err(|_| {
|
|
|
+ OIDCError(
|
|
|
+ OIDCErrorType::InvalidRequest,
|
|
|
+ "No such realm!".to_string(),
|
|
|
+ state,
|
|
|
+ )
|
|
|
+ })?;
|
|
|
|
|
|
- if sauth.is_none() {
|
|
|
- // if we don't have any relevant auth info, redirect to login
|
|
|
+ let make_redirect = || {
|
|
|
let mut login_url = request.url().join("../session/login").unwrap();
|
|
|
login_url
|
|
|
.query_pairs_mut()
|
|
|
.clear()
|
|
|
.append_pair("redirect", request.url().as_str());
|
|
|
return Ok(tide::Redirect::new(login_url).into());
|
|
|
- }
|
|
|
- let sauth = sauth.unwrap();
|
|
|
+ };
|
|
|
+
|
|
|
+ let potential_sauth = shelper
|
|
|
+ .get_session(&request)
|
|
|
+ .and_then(|session| shelper.get_auth_for_session(realm.id(), &session));
|
|
|
+
|
|
|
+ let Some(user_id) = potential_sauth.and_then(|v| v.user) else {
|
|
|
+ // if we don't have any relevant auth info, redirect to login
|
|
|
+ return make_redirect();
|
|
|
+ };
|
|
|
+
|
|
|
+ let Ok(Some(user)) = realm
|
|
|
+ .users
|
|
|
+ .with(schema::UserID::default(), user_id)
|
|
|
+ .first()
|
|
|
+ .get()
|
|
|
+ else {
|
|
|
+ return Err(OIDCError(
|
|
|
+ OIDCErrorType::ServerError,
|
|
|
+ "Internal state error!".to_string(),
|
|
|
+ state,
|
|
|
+ ));
|
|
|
+ };
|
|
|
|
|
|
let scopes = qp
|
|
|
.scope
|
|
@@ -89,12 +112,13 @@ fn do_token_authorize(
|
|
|
.unwrap_or("")
|
|
|
.split_whitespace();
|
|
|
|
|
|
+ // TODO: check that redirect URI matches
|
|
|
+
|
|
|
let token = token::generate_auth_token(
|
|
|
&request.state().core.config,
|
|
|
- &qi,
|
|
|
- shelper.get_realm().unwrap(),
|
|
|
- client.id(),
|
|
|
- sauth.user,
|
|
|
+ &realm,
|
|
|
+ client.as_ref(),
|
|
|
+ user.as_ref(),
|
|
|
scopes,
|
|
|
);
|
|
|
|
|
@@ -108,50 +132,46 @@ fn do_token_authorize(
|
|
|
}
|
|
|
|
|
|
fn do_authorize(request: Request, state: Option<&str>) -> Result<tide::Response, OIDCError> {
|
|
|
+ let shelper = SessionHelper::new(&request);
|
|
|
+ let realm = shelper.get_realm().map_err(|_| {
|
|
|
+ OIDCError(
|
|
|
+ OIDCErrorType::InvalidRequest,
|
|
|
+ "No such realm!".to_string(),
|
|
|
+ state,
|
|
|
+ )
|
|
|
+ })?;
|
|
|
+
|
|
|
let qp: AuthorizeQueryParams = request
|
|
|
.query()
|
|
|
.map_err(|x| OIDCError(OIDCErrorType::InvalidRequest, x.to_string(), state))?;
|
|
|
|
|
|
// verify the realm and client_id and redirect_uri
|
|
|
- let qi = request.state().core.pool.query_interface();
|
|
|
- let realm = qi
|
|
|
- .get()
|
|
|
- .by(schema::Realm::Shortname, &request.param("realm").unwrap())
|
|
|
- .one()
|
|
|
- .expect("couldn't query db");
|
|
|
- if realm.is_none() {
|
|
|
- return Err(OIDCError(
|
|
|
- OIDCErrorType::InvalidRequest,
|
|
|
- "No such realm!".to_string(),
|
|
|
- state,
|
|
|
- ));
|
|
|
- }
|
|
|
- let realm = realm.unwrap();
|
|
|
|
|
|
- let client = qi
|
|
|
+ let client = realm
|
|
|
+ .clients
|
|
|
+ .with(schema::Client::Shortname, &qp.client_id)
|
|
|
+ .first()
|
|
|
.get()
|
|
|
- .by(schema::Client::Realm, &realm.id())
|
|
|
- .by(schema::Client::Shortname, &qp.client_id)
|
|
|
- .one()
|
|
|
- .expect("couldn't query db");
|
|
|
- if client.is_none() {
|
|
|
- return Err(OIDCError(
|
|
|
- OIDCErrorType::UnauthorizedClient,
|
|
|
- "Client does not exist".to_string(),
|
|
|
- state,
|
|
|
- ));
|
|
|
- }
|
|
|
+ .ok()
|
|
|
+ .flatten()
|
|
|
+ .ok_or_else(|| {
|
|
|
+ OIDCError(
|
|
|
+ OIDCErrorType::UnauthorizedClient,
|
|
|
+ "Client does not exist".to_string(),
|
|
|
+ state,
|
|
|
+ )
|
|
|
+ })?;
|
|
|
|
|
|
if qp.response_type == "code" {
|
|
|
- do_code_authorize(request, qp, state, client.unwrap())
|
|
|
+ do_code_authorize(request, qp, state, client)
|
|
|
} else if qp.response_type == "token" {
|
|
|
- do_token_authorize(request, qp, state, client.unwrap())
|
|
|
+ do_token_authorize(request, qp, state, client)
|
|
|
} else {
|
|
|
- return Err(OIDCError(
|
|
|
+ Err(OIDCError(
|
|
|
OIDCErrorType::UnsupportedResponseType,
|
|
|
"Only code and token are understood.".to_string(),
|
|
|
state,
|
|
|
- ));
|
|
|
+ ))
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -193,35 +213,46 @@ const TOKEN_PATH: &'static str = "oidc/token";
|
|
|
const JWKS_PATH: &'static str = "oidc/jwks";
|
|
|
|
|
|
async fn jwks(request: Request) -> tide::Result<tide::Response> {
|
|
|
- let qi = request.state().core.pool.query_interface();
|
|
|
-
|
|
|
let shelper = super::session::SessionHelper::new(&request);
|
|
|
let realm = shelper.get_realm()?;
|
|
|
-
|
|
|
- let keyinfo = qi
|
|
|
- .get()
|
|
|
- .by(schema::Key::Realm, &realm)
|
|
|
- .all()?
|
|
|
- .into_iter()
|
|
|
- .map(|key| {
|
|
|
- let kpair = ring::signature::Ed25519KeyPair::from_pkcs8(&key.keydata)
|
|
|
- .expect("couldn't parse keypair in db");
|
|
|
- let pubkey_bytes = kpair.public_key().as_ref();
|
|
|
- assert_eq!(pubkey_bytes.len(), 32);
|
|
|
-
|
|
|
- serde_json::json!({
|
|
|
- "kty": "OKP",
|
|
|
- "crv": "Ed25519",
|
|
|
- "x": base64::encode(pubkey_bytes),
|
|
|
- "kid": key.key_id,
|
|
|
- })
|
|
|
- });
|
|
|
+ // let rkeys = key::RealmKeys::new(realm.wrapped());
|
|
|
+
|
|
|
+ let keyinfo =
|
|
|
+ realm
|
|
|
+ .keys
|
|
|
+ .get()?
|
|
|
+ .into_iter()
|
|
|
+ .map(|key| match key::ParsedKey::parse_from(&key)? {
|
|
|
+ key::ParsedKey::Ed25519 { key_id: _, keypair } => Ok(serde_json::json!({
|
|
|
+ "crv": "Ed25519",
|
|
|
+ "kid": key.key_id,
|
|
|
+ "kty": "OKP",
|
|
|
+ "use": "sig",
|
|
|
+
|
|
|
+ "x": base64::encode(keypair.public_key().as_ref()),
|
|
|
+ })),
|
|
|
+ key::ParsedKey::RSA { key_id: _, keypair } => {
|
|
|
+ let pubkey = keypair.public_key();
|
|
|
+ Ok(serde_json::json!({
|
|
|
+ "alg": "RS256",
|
|
|
+ "kid": key.key_id,
|
|
|
+ "kty": "RSA",
|
|
|
+ "use": "sig",
|
|
|
+
|
|
|
+ "e": base64::encode(pubkey.exponent().big_endian_without_leading_zero()),
|
|
|
+ "n": base64::encode(pubkey.modulus().big_endian_without_leading_zero()),
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
let jwks_response = serde_json::json!({
|
|
|
- "keys": keyinfo.collect::<Vec<_>>(),
|
|
|
+ "keys": keyinfo.collect::<Result<Vec<_>, UIDCError>>()?,
|
|
|
});
|
|
|
|
|
|
- Ok(tide::Response::builder(200).body(jwks_response).build())
|
|
|
+ Ok(tide::Response::builder(200)
|
|
|
+ .header(tide::http::headers::ACCESS_CONTROL_ALLOW_ORIGIN, "*")
|
|
|
+ .body(jwks_response)
|
|
|
+ .build())
|
|
|
}
|
|
|
|
|
|
async fn discovery_config(request: Request) -> tide::Result<tide::Response> {
|
|
@@ -237,13 +268,16 @@ async fn discovery_config(request: Request) -> tide::Result<tide::Response> {
|
|
|
"authorization_endpoint": format!("{}/{}", base_url, AUTHORIZE_PATH),
|
|
|
"token_endpoint": format!("{}/{}", base_url, TOKEN_PATH),
|
|
|
"jwks_uri": format!("{}/{}", base_url, JWKS_PATH),
|
|
|
- "token_endpoint_auth_signing_alg_values_supported": ["EdDSA"],
|
|
|
+ "token_endpoint_auth_signing_alg_values_supported": ["EdDSA", "RS256"],
|
|
|
"response_types_supported": ["code", "id_token", "token id_token"],
|
|
|
"subject_types_supported": ["public"],
|
|
|
- "id_token_signing_alg_values_supported": ["EdDSA"],
|
|
|
+ "id_token_signing_alg_values_supported": ["EdDSA", "RS256"],
|
|
|
});
|
|
|
|
|
|
- Ok(tide::Response::builder(200).body(config_response).build())
|
|
|
+ Ok(tide::Response::builder(200)
|
|
|
+ .header(tide::http::headers::ACCESS_CONTROL_ALLOW_ORIGIN, "*")
|
|
|
+ .body(config_response)
|
|
|
+ .build())
|
|
|
}
|
|
|
|
|
|
pub(super) fn oidc_server(mut route: tide::Route<super::ServerStateWrapper>) {
|