use serde::{Deserialize, Serialize}; use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp}; use crate::model::oauth::AppScope; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub enum AppQuota { /// The app is limited to 5 grants. Limited, /// The app is allowed to maintain an unlimited number of grants. Unlimited, } impl Default for AppQuota { fn default() -> Self { Self::Limited } } /// An app is required to request grants on user accounts. /// /// Users must approve grants through a web portal. #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ThirdPartyApp { pub id: usize, pub created: usize, /// The ID of the owner of the app. pub owner: usize, /// The name of the app. pub title: String, /// The URL of the app's homepage. pub homepage: String, /// The redirect URL for the app. /// /// Upon accepting a grant request, the user will be redirected to this URL /// with a query parameter named `token`, which should be saved by the app /// for future authentication. /// /// The developer dashboard lists the URL you should send users to in order to /// create a grant on their account in the information section under the label /// "Grant URL". /// /// Any search parameters sent with your grant URL (such as an internal user ID) /// will also be sent back when the user is redirected to your redirect URL. /// /// You can use this behaviour to keep track of what user you should save the grant /// token under. /// /// 1. Redirect user to grant URL with their ID: `{grant_url}?my_app_user_id={id}` /// 2. In your redirect endpoint, read that ID and the added `token` parameter to /// store the `token` under the given `my_app_user_id` /// /// The redirect URL will also have a `verifier` search parameter appended. /// This verifier is required to refresh the grant's token (which is what is /// used in the `Atto-Grant` cookie). /// /// Tokens only last a week after they were generated (with the verifier), /// but you can refresh them by sending a request to: /// `{tetratto}/api/v1/auth/user/{user_id}/grants/{app_id}/refresh`. /// /// Tetratto will generate the verifier and challenge for you. The challenge /// is an SHA-256 hashed + base64 url encoded version of the verifier. This means /// if the verifier doesn't match, it won't pass the challenge. /// /// Requests to API endpoints using your grant token should be sent with a /// cookie (in the `Cookie` header) named `Atto-Grant`. This cookie should /// contain the token you received from either the initial connection, /// or a token refresh. pub redirect: String, /// The app's quota status, which determines how many grants the app is allowed to maintain. pub quota_status: AppQuota, /// If the app is banned. A banned app cannot use any of its grants. pub banned: bool, /// The number of accepted grants the app maintains. pub grants: usize, /// The scopes used for every grant the app maintains. /// /// These scopes are only cloned into **new** grants created for the app. /// An app *cannot* change scopes and have them affect users who already have the /// app connected. Users must delete the app's grant and authenticate it again /// to update their scopes. /// /// Your app should handle informing users when scopes change. pub scopes: Vec, } impl ThirdPartyApp { /// Create a new [`ThirdPartyApp`]. pub fn new(title: String, owner: usize, homepage: String, redirect: String) -> Self { Self { id: Snowflake::new().to_string().parse::().unwrap(), created: unix_epoch_timestamp(), owner, title, homepage, redirect, quota_status: AppQuota::Limited, banned: false, grants: 0, scopes: Vec::new(), } } }