Skip to content

Commit

Permalink
Merge pull request #6 from Authress/add-login-authorization-endpoints
Browse files Browse the repository at this point in the history
Add login authorization endpoints
  • Loading branch information
wparad authored Dec 24, 2023
2 parents 6a8ef4b + d3ea76d commit cfa1d7b
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 969 deletions.
65 changes: 59 additions & 6 deletions examples/server/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,48 @@ pub struct OpenIdConfiguration {
pub jwk_uri: String
}

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Default, Serialize, Deserialize)]
struct JwtPayload {
aud: String, // Optional. Audience
exp: usize, // Required (validate_exp defaults to true in validation). Expiration time (as UTC timestamp)
iat: usize, // Optional. Issued at (as UTC timestamp)
iss: String, // Optional. Issuer
sub: String, // Optional. Subject (whom token refers to)

// #[serde(deserialize_with = "swagger::nullable_format::deserialize_optional_nullable")]
// #[serde(default = "swagger::nullable_format::default_optional_nullable")]
#[serde(skip_serializing_if="Option::is_none")]
email: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
picture: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
name: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
preferred_username: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
phone_number: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
given_name: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
middle_name: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
family_name: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
nickname: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
birthdate: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
profile: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
website: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
pronouns: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
zoneinfo: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
locale: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
address: Option<String>
}

impl Default for SignatureKey {
Expand All @@ -61,6 +96,27 @@ impl SignatureKey {
iat: usize::try_from(chrono::offset::Local::now().timestamp()).unwrap(),
iss: format!("https//{host}"),
sub: "me".to_owned(),
..Default::default()
};
let mut header = Header::new(Algorithm::EdDSA);
header.kid = Some("authress-local".to_owned());
let signing_key = EncodingKey::from_ed_pem(&self.key_pair.to_pkcs8_pem(ed25519::pkcs8::spki::der::pem::LineEnding::LF).unwrap().as_bytes()).unwrap();
let token = encode(&header, &payload, &signing_key).unwrap();
return token;
}

pub fn create_id_token(&self, host: &str) -> String {
let payload = JwtPayload {
aud: "localhost".to_owned(),
exp: usize::try_from(chrono::offset::Local::now().add(Duration::days(1)).timestamp()).unwrap(),
iat: usize::try_from(chrono::offset::Local::now().timestamp()).unwrap(),
iss: format!("https//{host}"),
sub: "me".to_owned(),
email: Some("me@local.com".to_owned()),
name: Some("Current Test User".to_owned()),
picture: Some("https://authress.io/app/img/logo.svg".to_owned()),
..Default::default()

};
let mut header = Header::new(Algorithm::EdDSA);
header.kid = Some("authress-local".to_owned());
Expand All @@ -75,16 +131,13 @@ impl SignatureKey {
};
}

pub fn get_jwks(&self) -> JwkList {
let jwk = JWK {
pub fn get_jwk(&self) -> JWK {
return JWK {
kty: "OKP".to_owned(),
crv: "Ed25519".to_owned(),
alg: "EdDSA".to_owned(),
kid: "authress-local".to_owned(),
x: engine::general_purpose::URL_SAFE_NO_PAD.encode(self.key_pair.verifying_key().to_bytes())
};
return JwkList {
keys: vec![jwk]
};
}
}
64 changes: 64 additions & 0 deletions examples/server/authentication_controller.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use authress_local::authentication::AuthenticationRequest;
use serde::{Serialize, Deserialize};
use url::Url;
use crate::{*, authentication::SignatureKey};

#[derive(Debug, Serialize, Deserialize)]
pub struct JwkList {
pub keys: Vec<authentication::JWK>
}

#[derive(Debug, Serialize, Deserialize)]
pub struct OpenIdConfiguration {
pub jwk_uri: String
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))]
pub struct StartAuthenticationResponse {
#[serde(rename = "authenticationUrl")]
pub authentication_url: String,

#[serde(rename = "authenticationRequestId")]
pub authentication_request_id: String
}

#[derive(Debug, Default, Clone, Copy)]
pub struct AuthenticationController {}

impl AuthenticationController {
pub fn start_authentication(&self, host: &str, authentication_request: AuthenticationRequest, signature_key: SignatureKey) -> StartAuthenticationResponse {
let request_id = "RequestId";

let access_token = signature_key.create_token(host);
let id_token = signature_key.create_id_token(host);

let url = Url::parse_with_params(&authentication_request.redirect_url,
&[("access_token", &access_token), ("id_token", &id_token), ("nonce", &request_id.to_string())]
).unwrap();

return StartAuthenticationResponse {
authentication_request_id: request_id.to_string(),
authentication_url: url.to_string()
}
}

pub fn get_token(&self, host: &str, signature_key: SignatureKey) -> String {
let token = signature_key.create_token(host);

// TODO This should be an Object and we should be setting things in the response URL
return token;
}

pub fn get_openid_configuration(&self, host: &str) -> OpenIdConfiguration {
return OpenIdConfiguration {
jwk_uri: format!("https//{host}/.well-known/openid-configuration/jwks")
};
}

pub fn get_jwks(&self, signature_key: SignatureKey) -> JwkList {
return JwkList {
keys: vec![signature_key.get_jwk()]
};
}
}
1 change: 1 addition & 0 deletions examples/server/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
mod server;
mod databases;
mod authentication;
mod authentication_controller;

use lazy_static::lazy_static;
use log::info;
Expand Down
34 changes: 17 additions & 17 deletions examples/server/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#![allow(unused_imports)]

use async_trait::async_trait;
use authress_local::authentication::{RequestTokenResponse, LoginResponse, AuthenticationResponse, OpenIdConfigurationResponse, JwksResponse};
use authress_local::authentication::{RequestTokenResponse, LoginResponse, AuthenticationResponse, OpenIdConfigurationResponse, JwksResponse, AuthenticationRequest};
use futures::{future, Stream, StreamExt, TryFutureExt, TryStreamExt};
use hyper::server::conn::Http;
use hyper::service::Service;
Expand All @@ -27,6 +27,7 @@ use authress_local::*;
use authress_local::server::MakeService;
use std::error::Error;

use crate::authentication_controller;
use crate::databases::{Databases, self};

pub async fn create(addr: &str, databases: &'static Databases) {
Expand Down Expand Up @@ -91,14 +92,16 @@ pub async fn create(addr: &str, databases: &'static Databases) {
#[derive(Copy, Clone)]
pub struct Server<C> {
marker: PhantomData<C>,
databases: &'static Databases
databases: &'static Databases,
authentication_controller: authentication_controller::AuthenticationController
}

impl<C> Server<C> {
pub fn new(databases: &'static Databases) -> Self {
Server {
marker: PhantomData,
databases: &databases
databases: &databases,
authentication_controller: authentication_controller::AuthenticationController::default()
}
}
}
Expand Down Expand Up @@ -408,28 +411,24 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
/* ********************************* */

/// Authenticate
async fn authenticate(&self, host: &str, context: &C) -> Result<AuthenticationResponse, ApiError> {
async fn authenticate(&self, host: &str, authentication_request: AuthenticationRequest, context: &C) -> Result<AuthenticationResponse, ApiError> {
let signature_key_db = self.databases.signature_key.lock().unwrap();

let result = signature_key_db.create_token(host);
let result = self.authentication_controller.start_authentication(host, authentication_request, signature_key_db.to_owned());
info!("authenticate({host})- X-Span-ID: {:?}", context.get().0.clone());
return Ok(AuthenticationResponse::Success(serde_json::to_string(&result).unwrap()));
}

/// Get OpenID Configuration
async fn open_id_configuration(&self, host: &str, context: &C) -> Result<OpenIdConfigurationResponse, ApiError> {
let signature_key_db = self.databases.signature_key.lock().unwrap();

let result = signature_key_db.get_openid_configuration(host);
async fn open_id_configuration(&self, host: &str, context: &C) -> Result<OpenIdConfigurationResponse, ApiError> {
let result = self.authentication_controller.get_openid_configuration(host);
info!("open_id_configuration({host})- X-Span-ID: {:?}", context.get().0.clone());
return Ok(OpenIdConfigurationResponse::Success(serde_json::to_string(&result).unwrap()));
}

/// Get OpenID Configuration JWKS
async fn jwks(&self, context: &C) -> Result<JwksResponse, ApiError> {
let signature_key_db = self.databases.signature_key.lock().unwrap();

let result = signature_key_db.get_jwks();
let result = self.authentication_controller.get_jwks(signature_key_db.to_owned());
info!("jwks()- X-Span-ID: {:?}", context.get().0.clone());
return Ok(JwksResponse::Success(serde_json::to_string(&result).unwrap()));
}
Expand All @@ -441,11 +440,12 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
Err(ApiError::NotImplementedError("This endpoint is not yet implemented".into()))
}

/// OAuth Token
async fn request_token(&self, o_auth_token_request: OAuthTokenRequest,context: &C) -> Result<RequestTokenResponse, ApiError> {

info!("request_token({:?}) - X-Span-ID: {:?}", o_auth_token_request, context.get().0.clone());
Err(ApiError::NotImplementedError("This endpoint is not yet implemented".into()))
/// Request Token
async fn request_token(&self, host: &str, context: &C) -> Result<RequestTokenResponse, ApiError> {
let signature_key_db = self.databases.signature_key.lock().unwrap();
let result = self.authentication_controller.get_token(host, signature_key_db.to_owned());
info!("authenticate({host})- X-Span-ID: {:?}", context.get().0.clone());
return Ok(RequestTokenResponse::Success(serde_json::to_string(&result).unwrap()));
}

/* ********************************* */
Expand Down
8 changes: 4 additions & 4 deletions src/authentication/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,8 @@ pub enum AuthenticationResponse {

#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct AuthenticationRequest {
// /// The client identifier to constrain the token to.
// #[serde(rename = "client_id")]
// pub client_id: String,
#[serde(rename = "redirectUrl")]
pub redirect_url: String,
// /// The secret associated with the client that authorizes the generation of token it's behalf. (Either the `client_secret` or the `code_verifier` is required)
// #[serde(rename = "client_secret", default, with = "::serde_with::rust::double_option", skip_serializing_if = "Option::is_none")]
// pub client_secret: Option<Option<String>>,
Expand All @@ -84,8 +83,9 @@ pub struct AuthenticationRequest {
}

impl AuthenticationRequest {
pub fn new(client_id: String) -> AuthenticationRequest {
pub fn new(redirect_url: String) -> AuthenticationRequest {
AuthenticationRequest {
redirect_url,
// client_id,
// client_secret: None,
// code_verifier: None,
Expand Down
Loading

0 comments on commit cfa1d7b

Please sign in to comment.