Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/axum_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use axum_extra::extract::{CookieJar, cookie::Cookie};
use http::StatusCode;
use serde::Deserialize;
use tiny_google_oidc::{
code::{AccessType, AdditionalScope, CodeRequest, UnCheckedCodeResponse},
code::{AccessType, AdditionalScope, CodeRequest, RawCodeResponse},
config::{Config, ConfigBuilder},
csrf_token::CSRFToken,
id_token::{IDToken, IDTokenRequest, send_id_token_req},
Expand Down Expand Up @@ -127,7 +127,7 @@ async fn call_back(
req: Request,
) -> Result<impl IntoResponse, StatusCode> {
// Construct UnCheckedCodeResponse
let code_res = UnCheckedCodeResponse::from_url(req).map_err(|e| {
let code_res = RawCodeResponse::new(req).map_err(|e| {
error!("Failed to parse url: {}", e);
StatusCode::INTERNAL_SERVER_ERROR
})?;
Expand Down
4 changes: 2 additions & 2 deletions examples/hyper/login_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use http_body_util::{BodyExt, Empty, combinators::BoxBody};
use hyper::body::Incoming;
use redis::{aio::ConnectionManager, cmd};
use tiny_google_oidc::{
code::{AccessType, AdditionalScope, CodeRequest, UnCheckedCodeResponse},
code::{AccessType, AdditionalScope, CodeRequest, RawCodeResponse},
config::Config,
csrf_token::CSRFToken,
id_token::{IDToken, IDTokenRequest, send_id_token_req},
Expand Down Expand Up @@ -98,7 +98,7 @@ impl LoginService {
.await?;

// Create UncheckedCodeResponse from url
let code_res = UnCheckedCodeResponse::from_url(&req)?;
let code_res = RawCodeResponse::new(&req)?;
// Consume the response and get the code
// Verify that the CSRFToken matches (Error if they do not match)
let code = code_res.exchange_with_code(&csrf_token)?;
Expand Down
51 changes: 24 additions & 27 deletions src/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@
//!
//! It provides the following key functionalities:
//! - Generating an authorization request URL (`CodeRequest`).
//! - Parsing and verifying the authorization code received from Google's callback (`UnCheckedCodeResponse`).
//! - Parsing and verifying the authorization code received from Google's callback (`RawCodeResponse`).
//!
//! # Key Structures and Features
//!
//! ## `CodeRequest`
//! A structure used to generate the authorization request URL.
//! - Includes parameters like CSRF token, scope, redirect URI, and nonce.
//!
//! ## `UnCheckedCodeResponse`
//! ## `RawCodeResponse`
//! Represents the authorization code response received from Google.
//! - This response must be validated using a CSRF token before it can be used.
//!
//! ## `Code`
//! Represents a verified authorization code that can be exchanged for tokens.
//! - This is obtained after validating the `UnCheckedCodeResponse` with a CSRF token.
//! - This is obtained after validating the `RawCodeResponse` with a CSRF token.
//!
//! # Examples
//! ## Generating an Authorization Request URL
Expand All @@ -38,7 +38,7 @@
//!
//! ## Handling the Callback and Verifying the Authorization Code
//! ```rust,no_run
//! let response = UnCheckedCodeResponse::from_url(req).unwrap();
//! let response = RawCodeResponse::new(req).unwrap();
//! // get stored CSRF token From DB(Redis, in memory ...)
//! let csrf_token = store.get("csrf_token_key")?;
//!
Expand All @@ -48,18 +48,18 @@
//! # Flow
//! 1. Generate a CSRF token (`CSRFToken`) and include it in the authorization request.
//! 2. Redirect the user to Google's authentication page.
//! 3. After authentication, Google redirects back with an authorization code (`UnCheckedCodeResponse`).
//! 4. Validate the CSRF token in the `UnCheckedCodeResponse` using `Code::new_with_verify_csrf()` or `UnCheckedCodeResponse::exchange_with_code()`.
//! 3. After authentication, Google redirects back with an authorization code (`RawCodeResponse`).
//! 4. Validate the CSRF token in the `RawCodeResponse` using `Code::new_with_verify_csrf()` or `RawCodeResponse::exchange_with_code()`.
//! 5. If validation succeeds, a `Code` is obtained, which can be exchanged for tokens.
//!
//! # Notes
//! - Always validate the CSRF token to ensure the integrity of the authentication flow.
//! - Do not use `UnCheckedCodeResponse` directly without verification.
//! - Do not use `RawCodeResponse` directly without verification.
use url::Url;

use crate::{
config::{AuthEndPoint, ClientID, Config, RedirectURI},
csrf_token::{CSRFToken, UnCheckedCSRFToken},
csrf_token::{CSRFToken, RawCSRFToken},
error::Error,
nonce::Nonce,
};
Expand All @@ -77,11 +77,11 @@ use std::{collections::HashMap, iter::Iterator};
///
/// # How to Create
/// A `Code` can only be created after validating the `CSRFToken`.
/// Use either `Code::new_with_verify_csrf` or `UnCheckedCodeResponse::exchange_with_code` to validate and create a `Code`.
/// Use either `Code::new_with_verify_csrf` or `RawCodeResponse::exchange_with_code` to validate and create a `Code`.
///
/// # Example
/// ```rust,no_run
/// let response = UnCheckedCodeResponse::from_url("https://example.com/callback?...").unwrap();
/// let response = RawCodeResponse::new(req).unwrap();
/// let csrf_token = store.get("csrf_token_key")?;
///
/// let code = response.exchange_with_code(csrf_token).expect("CSRF token mismatch!");
Expand All @@ -96,10 +96,7 @@ pub struct Code(pub(crate) String);
impl Code {
/// Checks if `res.state` (CSRF token from Google) matches `csrf_token` (generated by user).
/// If valid, returns a `Code`; otherwise, returns `Error::CSRFNotMatch`.
pub fn new_with_verify_csrf(
res: UnCheckedCodeResponse,
csrf_token_val: &str,
) -> Result<Self, Error> {
pub fn new_with_verify_csrf(res: RawCodeResponse, csrf_token_val: &str) -> Result<Self, Error> {
if res.state.0 == csrf_token_val {
Ok(res.code)
} else {
Expand Down Expand Up @@ -208,26 +205,26 @@ impl<'a> CodeRequest<'a> {
/// Must be validated using a CSRF token before use.
/// # Example
/// ```rust,no_run
/// let response = UnCheckedCodeResponse::from_url(req).unwrap();
/// let response = RawCodeResponse::new(req).unwrap();
/// let csrf_token = store.get("csrf_token_key")?;
///
/// let code = response.exchange_with_code(csrf_token).expect("CSRF token mismatch!");
/// ```
#[derive(Debug, Clone)]
pub struct UnCheckedCodeResponse {
state: UnCheckedCSRFToken,
pub struct RawCodeResponse {
state: RawCSRFToken,
code: Code,
}

impl UnCheckedCodeResponse {
pub fn from_url<Q>(query_src: Q) -> Result<Self, Error>
impl RawCodeResponse {
pub fn new<Q>(query_src: Q) -> Result<Self, Error>
where
Q: QueryExtractor,
{
let query_str = query_src.extract_query().ok_or(Error::ParamsNotFound)?;
let params: HashMap<_, _> = url::form_urlencoded::parse(query_str.as_bytes()).collect();
Ok(Self {
state: UnCheckedCSRFToken(
state: RawCSRFToken(
params
.get("state")
.ok_or(Error::ParamsNotFound)?
Expand Down Expand Up @@ -338,7 +335,7 @@ mod tests {

use crate::{code::AccessType, config::ConfigBuilder, csrf_token::CSRFToken, nonce::Nonce};

use super::{AdditionalScope, CodeRequest, UnCheckedCodeResponse};
use super::{AdditionalScope, CodeRequest, RawCodeResponse};

#[test]
fn test_code_req_offline() {
Expand Down Expand Up @@ -559,21 +556,21 @@ mod tests {
let uri = format!("https://www.example.com/autu?code={}&state={}", code, state);
let http_req = http::Request::builder().uri(uri).body(()).unwrap();

let uncheck_code_res = UnCheckedCodeResponse::from_url(http_req);
let raw_code_res = RawCodeResponse::new(http_req);

assert!(uncheck_code_res.is_ok());
assert!(raw_code_res.is_ok());

assert_eq!(uncheck_code_res.clone().unwrap().state.0, "mystate");
assert_eq!(uncheck_code_res.unwrap().code.0, "mycode");
assert_eq!(raw_code_res.clone().unwrap().state.0, "mystate");
assert_eq!(raw_code_res.unwrap().code.0, "mycode");
}

#[test]
fn test_construct_uncheck_code_res_none_params() {
let uri = format!("https://www.example.com/");
let http_req = http::Request::builder().uri(uri).body(()).unwrap();

let uncheck_code_res = UnCheckedCodeResponse::from_url(http_req);
let raw_code_res = RawCodeResponse::new(http_req);

assert!(uncheck_code_res.is_err());
assert!(raw_code_res.is_err());
}
}
2 changes: 1 addition & 1 deletion src/csrf_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl CSRFToken {
///
/// This token **has not been verified yet** and should be checked against the stored `CSRFToken` before proceeding.
#[derive(Debug, Clone)]
pub struct UnCheckedCSRFToken(pub(crate) String);
pub struct RawCSRFToken(pub(crate) String);

#[cfg(test)]
mod tests {
Expand Down
Loading