Skip to content

Commit

Permalink
feat: add Error to display error info (#15)
Browse files Browse the repository at this point in the history
* feat: add json error and base64 decode error

* feat: update jwt_parser return self.Error

* feat: update comments

* feat: change to self.Result for validate.id_token.rs

* feat: update

* feat: change async_client result to self.result

* feat: change client.result to self.result

* fix: import self.result

* doc: update

* fix: doc

* fix: adapt for wasm
  • Loading branch information
caojen committed Nov 16, 2023
1 parent 798f35d commit 5e0b1ff
Show file tree
Hide file tree
Showing 8 changed files with 321 additions and 30 deletions.
8 changes: 4 additions & 4 deletions src/async_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::time::{Duration, Instant};
use lazy_static::lazy_static;
use log::debug;
use async_rwlock::RwLock;
use crate::{DEFAULT_TIMEOUT, GOOGLE_OAUTH_V3_USER_INFO_API, GOOGLE_SA_CERTS_URL, GoogleAccessTokenPayload, GooglePayload, utils};
use crate::{DEFAULT_TIMEOUT, GOOGLE_OAUTH_V3_USER_INFO_API, GOOGLE_SA_CERTS_URL, GoogleAccessTokenPayload, GooglePayload, MyResult, utils};
use crate::certs::{Cert, Certs};
use crate::jwt_parser::JwtParser;
use crate::validate::id_token;
Expand Down Expand Up @@ -57,7 +57,7 @@ impl AsyncClient {
}

/// Do verification with `id_token`. If succeed, return the user data.
pub async fn validate_id_token<S>(&self, token: S) -> anyhow::Result<GooglePayload>
pub async fn validate_id_token<S>(&self, token: S) -> MyResult<GooglePayload>
where S: AsRef<str>
{
let token = token.as_ref();
Expand All @@ -73,7 +73,7 @@ impl AsyncClient {
Ok(parser.payload)
}

async fn get_cert(&self, alg: &str, kid: &str) -> anyhow::Result<Cert> {
async fn get_cert(&self, alg: &str, kid: &str) -> MyResult<Cert> {
{
let cached_certs = self.cached_certs.read().await;
if !cached_certs.need_refresh() {
Expand Down Expand Up @@ -106,7 +106,7 @@ impl AsyncClient {
}

/// Try to validate access token. If succeed, return the user info.
pub async fn validate_access_token<S>(&self, token: S) -> anyhow::Result<GoogleAccessTokenPayload>
pub async fn validate_access_token<S>(&self, token: S) -> MyResult<GoogleAccessTokenPayload>
where S: AsRef<str>
{
let token = token.as_ref();
Expand Down
6 changes: 3 additions & 3 deletions src/certs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
use std::time::Instant;
#[cfg(feature = "wasm")]
use web_time::Instant;
use anyhow::bail;
use log::debug;
use serde::{Deserialize, Serialize};
use crate::{IDTokenCertNotFoundError, MyResult};

#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Certs {
Expand All @@ -27,13 +27,13 @@ pub struct Cert {
}

impl Certs {
pub fn find_cert<T: AsRef<str>>(&self, alg: T, kid: T) -> anyhow::Result<Cert> {
pub fn find_cert<T: AsRef<str>>(&self, alg: T, kid: T) -> MyResult<Cert> {
let alg = alg.as_ref();
let kid = kid.as_ref();

match self.keys.iter().find(|cert| cert.alg == alg && cert.kid == kid) {
Some(cert ) => Ok(cert.clone()),
None => bail!("alg {}, kid = {} not found in google certs", alg, kid),
None => Err(IDTokenCertNotFoundError::new(alg, kid))?,
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use log::debug;
use crate::certs::{Cert, Certs};
use crate::jwt_parser::JwtParser;
use crate::validate::id_token;
use crate::MyResult;

lazy_static! {
static ref cb: reqwest::blocking::Client = reqwest::blocking::Client::new();
Expand Down Expand Up @@ -58,7 +59,7 @@ impl Client {
}

/// Do verification with `id_token`. If succeed, return the user data.
pub fn validate_id_token<S>(&self, token: S) -> anyhow::Result<GooglePayload>
pub fn validate_id_token<S>(&self, token: S) -> MyResult<GooglePayload>
where S: AsRef<str>
{
let token = token.as_ref();
Expand All @@ -74,7 +75,7 @@ impl Client {
Ok(parser.payload)
}

fn get_cert(&self, alg: &str, kid: &str) -> anyhow::Result<Cert> {
fn get_cert(&self, alg: &str, kid: &str) -> MyResult<Cert> {
{
let cached_certs = self.cached_certs.read().unwrap();
if !cached_certs.need_refresh() {
Expand Down Expand Up @@ -106,7 +107,7 @@ impl Client {
}

/// Try to validate access token. If succeed, return the user info.
pub fn validate_access_token<S>(&self, token: S) -> anyhow::Result<GoogleAccessTokenPayload>
pub fn validate_access_token<S>(&self, token: S) -> MyResult<GoogleAccessTokenPayload>
where S: AsRef<str>
{
let token = token.as_ref();
Expand Down
278 changes: 278 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
use std::fmt::{Debug, Display, Formatter};
use crate::GOOGLE_ISS;
#[cfg(feature = "wasm")]
use web_time::SystemTimeError;
#[cfg(not(feature = "wasm"))]
use std::time::SystemTimeError;

pub type Result<T> = core::result::Result<T, Error>;

#[derive(Debug)]
pub enum Error {
/// Any JSON error from [serde_json]
JsonError(serde_json::Error),
/// Any base64 decode error, from [base64]
Base64DecodeError(base64::DecodeError),
/// Error when id_token splits into 3 parts
IDTokenSplitError(IDTokenSplitError),
/// Error when id_token is expired
IDTokenExpiredError(IDTokenExpiredError),
/// Any [SystemTimeError]
SystemTimeError(SystemTimeError),
/// Error when id_token has an issuer which not listed in [GOOGLE_ISS]
GoogleIssuerNotMatchError(GoogleIssuerNotMatchError),
/// Error when id_token has a client_id which not listed when client was created.
IDTokenClientIDNotFoundError(IDTokenClientIDNotFoundError),
/// Any [rsa::signature::Error]
RS256SignatureError(rsa::signature::Error),
/// Any [rsa::errors::Error]
RS256Error(rsa::errors::Error),
/// Error when id_token has an unimplemented hash algorithm
HashAlgorithmUnimplementedError(HashAlgorithmUnimplementedError),
/// Error when alg and kid (in header of id_token) not found in any cert from google server
IDTokenCertNotFoundError(IDTokenCertNotFoundError),
/// Any [reqwest::Error]
ReqwestError(reqwest::Error),
}

impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::JsonError(e) => Display::fmt(&e, f),
Self::Base64DecodeError(e) => Display::fmt(&e, f),
Self::IDTokenSplitError(e) => Display::fmt(&e, f),
Self::IDTokenExpiredError(e) => Display::fmt(&e, f),
Self::SystemTimeError(e) => Display::fmt(&e, f),
Self::GoogleIssuerNotMatchError(e) => Display::fmt(&e, f),
Self::IDTokenClientIDNotFoundError(e) => Display::fmt(&e, f),
Self::RS256SignatureError(e) => Display::fmt(&e, f),
Self::RS256Error(e) => Display::fmt(&e, f),
Self::HashAlgorithmUnimplementedError(e) => Display::fmt(&e, f),
Self::IDTokenCertNotFoundError(e) => Display::fmt(&e, f),
Self::ReqwestError(e) => Display::fmt(&e, f),
}
}
}

impl std::error::Error for Error {}

impl From<serde_json::Error> for Error {
#[inline]
fn from(err: serde_json::Error) -> Self {
Self::JsonError(err)
}
}

impl From<base64::DecodeError> for Error {
#[inline]
fn from(err: base64::DecodeError) -> Self {
Self::Base64DecodeError(err)
}
}

#[derive(Debug, Clone, Copy)]
pub struct IDTokenSplitError {
pub expected: usize,
pub get: usize,
}

impl Display for IDTokenSplitError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "id_token split error, expected {} segments, but get {}", self.expected, self.get)
}
}

impl std::error::Error for IDTokenSplitError {}

impl From<IDTokenSplitError> for Error {
#[inline]
fn from(err: IDTokenSplitError) -> Self {
Self::IDTokenSplitError(err)
}
}

impl IDTokenSplitError {
#[inline]
pub fn new(expected: usize, get: usize) -> Self {
Self { expected, get }
}
}

#[derive(Debug)]
pub struct IDTokenExpiredError {
pub now: u64,
pub exp: u64,
}

impl IDTokenExpiredError {
#[inline]
pub fn new(now: u64, exp: u64) -> Self {
Self { now, exp }
}
}

impl From<IDTokenExpiredError> for Error {
#[inline]
fn from(err: IDTokenExpiredError) -> Self {
Self::IDTokenExpiredError(err)
}
}

impl Display for IDTokenExpiredError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "token expired, {} > {}", self.now, self.exp)
}
}

impl std::error::Error for IDTokenExpiredError {}

impl From<SystemTimeError> for Error {
#[inline]
fn from(err: SystemTimeError) -> Self {
Self::SystemTimeError(err)
}
}

#[derive(Debug)]
pub struct GoogleIssuerNotMatchError {
pub get: String,
pub expected: [&'static str; 2],
}

impl GoogleIssuerNotMatchError {
#[inline]
pub fn new<S: ToString>(get: S) -> Self {
Self {
get: get.to_string(),
expected: GOOGLE_ISS,
}
}
}

impl Display for GoogleIssuerNotMatchError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "id_token issue error, iss = {}, but expects one of {:?}", self.get, self.expected)
}
}

impl std::error::Error for GoogleIssuerNotMatchError {}

impl From<GoogleIssuerNotMatchError> for Error {
#[inline]
fn from(err: GoogleIssuerNotMatchError) -> Self {
Self::GoogleIssuerNotMatchError(err)
}
}

#[derive(Debug)]
pub struct IDTokenClientIDNotFoundError {
pub get: String,
pub expected: Vec<String>,
}

impl Display for IDTokenClientIDNotFoundError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "id_token client_id not found, get {}, but expected one of {:?}", &self.get, &self.expected)
}
}

impl std::error::Error for IDTokenClientIDNotFoundError {}

impl From<IDTokenClientIDNotFoundError> for Error {
fn from(err: IDTokenClientIDNotFoundError) -> Self {
Self::IDTokenClientIDNotFoundError(err)
}
}

impl IDTokenClientIDNotFoundError {
pub fn new<S, T, V>(get: S, expected: T) -> Self
where
S: ToString,
T: AsRef<[V]>,
V: AsRef<str>
{
Self {
get: get.to_string(),
expected: expected.as_ref().iter().map(|e| e.as_ref().to_string()).collect(),
}
}
}

impl From<rsa::signature::Error> for Error {
#[inline]
fn from(err: rsa::signature::Error) -> Self {
Self::RS256SignatureError(err)
}
}

impl From<rsa::errors::Error> for Error {
#[inline]
fn from(err: rsa::errors::Error) -> Self {
Self::RS256Error(err)
}
}

#[derive(Debug)]
pub struct HashAlgorithmUnimplementedError {
pub get: String,
}

impl HashAlgorithmUnimplementedError {
#[inline]
pub fn new<S: ToString>(get: S) -> Self {
Self { get: get.to_string() }
}
}

impl Display for HashAlgorithmUnimplementedError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "id_token: unimplemented hash alg: {}", self.get)
}
}

impl std::error::Error for HashAlgorithmUnimplementedError {}

impl From<HashAlgorithmUnimplementedError> for Error {
#[inline]
fn from(err: HashAlgorithmUnimplementedError) -> Self {
Self::HashAlgorithmUnimplementedError(err)
}
}

#[derive(Debug)]
pub struct IDTokenCertNotFoundError {
alg: String,
kid: String,
}

impl IDTokenCertNotFoundError {
#[inline]
pub fn new<S: ToString>(alg: S, kid: S) -> Self {
Self {
alg: alg.to_string(),
kid: kid.to_string(),
}
}
}

impl Display for IDTokenCertNotFoundError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "alg={}, kid={} not found in google certs", &self.alg, &self.kid)
}
}

impl std::error::Error for IDTokenCertNotFoundError {}

impl From<IDTokenCertNotFoundError> for Error {
#[inline]
fn from(err: IDTokenCertNotFoundError) -> Self {
Self::IDTokenCertNotFoundError(err)
}
}

impl From<reqwest::Error> for Error {
#[inline]
fn from(err: reqwest::Error) -> Self {
Self::ReqwestError(err)
}
}
Loading

0 comments on commit 5e0b1ff

Please sign in to comment.