///| Authorization Request Implementation
///|
/// AuthorizationRequest represents an OAuth2 authorization request
pub struct AuthorizationRequest {
auth_url : AuthUrl
client_id : ClientId
redirect_uri : RedirectUrl
scope : Array[Scope]
state : CsrfToken
response_type : String
pkce_challenge : PkceCodeChallenge?
nonce : Nonce? // OIDC: nonce parameter for replay attack protection
} derive(Show)
///|
/// Create a new AuthorizationRequest
pub fn AuthorizationRequest::new(
auth_url : AuthUrl,
client_id : ClientId,
redirect_uri : RedirectUrl,
scope : Array[Scope],
state : CsrfToken,
) -> AuthorizationRequest {
{
auth_url,
client_id,
redirect_uri,
scope,
state,
response_type: "code",
pkce_challenge: None,
nonce: None,
}
}
///|
/// Create a new AuthorizationRequest with PKCE support
pub fn AuthorizationRequest::new_with_pkce(
auth_url : AuthUrl,
client_id : ClientId,
redirect_uri : RedirectUrl,
scope : Array[Scope],
state : CsrfToken,
pkce_challenge : PkceCodeChallenge,
) -> AuthorizationRequest {
{
auth_url,
client_id,
redirect_uri,
scope,
state,
response_type: "code",
pkce_challenge: Some(pkce_challenge),
nonce: None,
}
}
///|
/// Generate a random CSRF token
/// Uses cryptographically secure random number generator (Chacha8 CSPRNG)
pub fn generate_csrf_token() -> CsrfToken {
let timestamp = "@builtin.timestamp_now"
let random_suffix = generate_random_suffix()
CsrfToken::new("state_\{timestamp}_\{random_suffix}")
}
///|
/// Generate a random nonce for OIDC
/// Uses cryptographically secure random number generator (Chacha8 CSPRNG)
pub fn generate_nonce() -> Nonce {
let timestamp = "@builtin.timestamp_now"
let random_suffix = generate_random_suffix()
Nonce::new("nonce_\{timestamp}_\{random_suffix}")
}
///|
/// Add nonce to an existing AuthorizationRequest (OIDC)
pub fn AuthorizationRequest::with_nonce(
self : AuthorizationRequest,
nonce : Nonce,
) -> AuthorizationRequest {
{ ..self, nonce: Some(nonce) }
}
///|
/// Generate a random suffix for state parameter
/// Uses cryptographically secure random number generator (Chacha8 CSPRNG)
fn generate_random_suffix() -> String {
let r = @random.Rand::new()
let chars = "abcdefghijklmnopqrstuvwxyz0123456789"
let char_count = chars.length()
let result = StringBuilder::new()
for i = 0; i < 16; i = i + 1 {
let idx = r
.uint(limit=char_count.reinterpret_as_uint())
.reinterpret_as_int()
result.write_char(chars[idx].unsafe_to_char())
}
result.to_string()
}
///|
/// Build the authorization URL with all parameters
pub fn AuthorizationRequest::build_authorization_url(
self : AuthorizationRequest,
) -> String {
let base_url = self.auth_url.to_string()
// Build query parameters
let params : Array[String] = []
// response_type=code (required)
params.push("response_type=\{self.response_type}")
// client_id (required)
let encoded_client_id = url_encode(self.client_id.to_string())
params.push("client_id=\{encoded_client_id}")
// redirect_uri (required)
let encoded_redirect = url_encode(self.redirect_uri.to_string())
params.push("redirect_uri=\{encoded_redirect}")
// scope (optional, but usually provided)
if self.scope.length() > 0 {
let scope_str = build_scope_string(self.scope)
let encoded_scope = url_encode(scope_str)
params.push("scope=\{encoded_scope}")
}
// state (recommended for CSRF protection)
let encoded_state = url_encode(self.state.to_string())
params.push("state=\{encoded_state}")
// PKCE parameters (optional, for enhanced security)
match self.pkce_challenge {
Some(challenge) => {
// code_challenge
let encoded_challenge = url_encode(challenge.to_string())
params.push("code_challenge=\{encoded_challenge}")
// code_challenge_method
params.push("code_challenge_method=\{challenge.method_string()}")
}
None => ()
}
// Nonce parameter (OIDC, for replay attack protection)
match self.nonce {
Some(nonce) => {
let encoded_nonce = url_encode(nonce.to_string())
params.push("nonce=\{encoded_nonce}")
}
None => ()
}
// Combine base URL with query parameters
let query_string = params.join("&")
if base_url.contains("?") {
"\{base_url}&\{query_string}"
} else {
"\{base_url}?\{query_string}"
}
}