use base64::{engine::general_purpose::URL_SAFE as base64url, Engine}; use serde::{Serialize, Deserialize}; use tetratto_shared::hash::hash; use super::{Result, Error}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AuthGrant { pub id: usize, /// The name of the application associated with this grant. pub name: String, /// The code challenge for PKCE verifiers associated with this grant. /// /// This challenge is *all* that is required to refresh this grant's auth token. /// While there can only be one token at a time, it can be refreshed whenever as long /// as the provided verifier matches that of the challenge. /// /// The challenge should never be changed. To change the challenge, the grant /// should be removed and recreated. pub challenge: String, /// The encoding method for the initial verifier in the challenge. pub method: PkceChallengeMethod, /// The access token associated with the account. This is **not** the same as /// regular account access tokens, as the token can only be used with the requested `scopes`. pub token: String, /// Scopes define what the grant's token is actually allowed to do. /// /// No scope shall ever be allowed to change scopes or manage grants on behalf of the user. /// A regular user token **must** be provided to manage grants. pub scopes: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum PkceChallengeMethod { S256, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum AppScope { /// Read the user's profile (username, bio, etc). UserReadProfile, /// Read the user's settings. UserReadSettings, /// Read the user's sessions and info. UserReadSessions, /// Read posts as the user. UserReadPosts, /// Read messages as the user. UserReadMessages, /// Create posts as the user. UserCreatePosts, /// Create messages as the user. UserCreateMessages, /// Delete posts owned by the user. UserDeletePosts, /// Delete messages owned by the user. UserDeleteMessages, /// Manage stacks owned by the user. UserManageStacks, /// Manage the user's following/unfollowing. UserManageRelationships, /// Manage the user's settings. UserManageSettings, } impl AppScope { /// Parse the given input string as a list of scopes. pub fn parse(input: &str) -> Vec { let mut out: Vec = Vec::new(); for scope in input.split(" ") { out.push(match scope { "user-read-profile" => Self::UserReadProfile, "user-read-settings" => Self::UserReadSettings, "user-read-sessions" => Self::UserReadSessions, "user-read-posts" => Self::UserReadPosts, "user-read-messages" => Self::UserReadMessages, "user-create-posts" => Self::UserCreatePosts, "user-create-messages" => Self::UserCreateMessages, "user-delete-posts" => Self::UserDeletePosts, "user-delete-messages" => Self::UserDeleteMessages, "user-manage-stacks" => Self::UserManageStacks, "user-manage-relationships" => Self::UserManageRelationships, "user-manage-settings" => Self::UserManageSettings, _ => continue, }) } out } } /// Check a verifier against the stored challenge (using the given [`PkceChallengeMethod`]). pub fn check_verifier(verifier: &str, challenge: &str, method: PkceChallengeMethod) -> Result<()> { if method != PkceChallengeMethod::S256 { return Err(Error::MiscError("only S256 is supported".to_string())); } let decoded = match base64url.decode(challenge.as_bytes()) { Ok(hash) => hash, Err(e) => return Err(Error::MiscError(e.to_string())), }; let hash = hash(verifier.to_string()); if hash.as_bytes() != decoded { // the verifier we received does not match the verifier from the stored challenge return Err(Error::NotAllowed); } Ok(()) }