add: user grants
TODO: add grant creation api, grant tab in sessions tab of settings, grant api endpoints
This commit is contained in:
parent
7de2c2e935
commit
bf27c51ad3
8 changed files with 98 additions and 7 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
debug/
|
||||||
|
|
|
@ -198,10 +198,11 @@ pub(crate) async fn replace_in_html(
|
||||||
let mut input = if !lisp {
|
let mut input = if !lisp {
|
||||||
input.to_string()
|
input.to_string()
|
||||||
} else {
|
} else {
|
||||||
|
let parsed = bberry::parse(input);
|
||||||
if let Some(plugins) = plugins {
|
if let Some(plugins) = plugins {
|
||||||
bberry::parse(input).render(plugins)
|
parsed.render(plugins)
|
||||||
} else {
|
} else {
|
||||||
bberry::parse(input).render_safe()
|
parsed.render_safe()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
("class" "card small flex items-center justify-between gap-2")
|
("class" "card small flex items-center justify-between gap-2")
|
||||||
(span
|
(span
|
||||||
("class" "flex items-center gap-2")
|
("class" "flex items-center gap-2")
|
||||||
(text "{{ icon scroll-text }}")
|
(icon (text "scroll-text"))
|
||||||
(span (text "{{ file_name }}"))))
|
(span (text "{{ file_name }}"))))
|
||||||
|
|
||||||
(div
|
(div
|
||||||
|
|
|
@ -582,7 +582,7 @@
|
||||||
method: "POST",
|
method: "POST",
|
||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then(async (res) => {
|
.then(async (_) => {
|
||||||
// create challenge and store
|
// create challenge and store
|
||||||
const verifier = await trigger("connections::pkce_verifier", [
|
const verifier = await trigger("connections::pkce_verifier", [
|
||||||
128,
|
128,
|
||||||
|
|
|
@ -3,6 +3,7 @@ use super::*;
|
||||||
use crate::cache::Cache;
|
use crate::cache::Cache;
|
||||||
use crate::model::auth::UserConnections;
|
use crate::model::auth::UserConnections;
|
||||||
use crate::model::moderation::AuditLogEntry;
|
use crate::model::moderation::AuditLogEntry;
|
||||||
|
use crate::model::oauth::AuthGrant;
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
Error, Result,
|
Error, Result,
|
||||||
auth::{Token, User, UserSettings},
|
auth::{Token, User, UserSettings},
|
||||||
|
@ -47,6 +48,7 @@ impl DataManager {
|
||||||
request_count: get!(x->16(i32)) as usize,
|
request_count: get!(x->16(i32)) as usize,
|
||||||
connections: serde_json::from_str(&get!(x->17(String)).to_string()).unwrap(),
|
connections: serde_json::from_str(&get!(x->17(String)).to_string()).unwrap(),
|
||||||
stripe_id: get!(x->18(String)),
|
stripe_id: get!(x->18(String)),
|
||||||
|
grants: serde_json::from_str(&get!(x->19(String)).to_string()).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +105,40 @@ impl DataManager {
|
||||||
Ok(res.unwrap())
|
Ok(res.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a user given just their grant token.
|
||||||
|
///
|
||||||
|
/// Also returns the auth grant this token is associated with from the user.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `token` - the token of the user
|
||||||
|
pub async fn get_user_by_grant_token(&self, token: &str) -> Result<(AuthGrant, User)> {
|
||||||
|
let conn = match self.connect().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = query_row!(
|
||||||
|
&conn,
|
||||||
|
"SELECT * FROM users WHERE grants::jsonb @> ('{\"token\":' || $1 || '}')::jsonb",
|
||||||
|
&[&token],
|
||||||
|
|x| Ok(Self::get_user_from_row(x))
|
||||||
|
);
|
||||||
|
|
||||||
|
if res.is_err() {
|
||||||
|
return Err(Error::UserNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = res.unwrap();
|
||||||
|
Ok((
|
||||||
|
user.grants
|
||||||
|
.iter()
|
||||||
|
.find(|x| x.token == token)
|
||||||
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
|
user,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new user in the database.
|
/// Create a new user in the database.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
@ -696,6 +732,7 @@ impl DataManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto_method!(update_user_tokens(Vec<Token>)@get_user_by_id -> "UPDATE users SET tokens = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
auto_method!(update_user_tokens(Vec<Token>)@get_user_by_id -> "UPDATE users SET tokens = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
||||||
|
auto_method!(update_user_grants(Vec<AuthGrant>)@get_user_by_id -> "UPDATE users SET grants = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
||||||
auto_method!(update_user_settings(UserSettings)@get_user_by_id -> "UPDATE users SET settings = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
auto_method!(update_user_settings(UserSettings)@get_user_by_id -> "UPDATE users SET settings = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
||||||
auto_method!(update_user_connections(UserConnections)@get_user_by_id -> "UPDATE users SET connections = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
auto_method!(update_user_connections(UserConnections)@get_user_by_id -> "UPDATE users SET connections = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
||||||
auto_method!(update_user_subscriptions(HashMap<usize, usize>)@get_user_by_id -> "UPDATE users SET subscriptions = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
auto_method!(update_user_subscriptions(HashMap<usize, usize>)@get_user_by_id -> "UPDATE users SET subscriptions = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::permissions::FinePermission;
|
use super::{oauth::AuthGrant, permissions::FinePermission};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use totp_rs::TOTP;
|
use totp_rs::TOTP;
|
||||||
use tetratto_shared::{
|
use tetratto_shared::{
|
||||||
|
@ -43,6 +43,9 @@ pub struct User {
|
||||||
/// The user's Stripe customer ID.
|
/// The user's Stripe customer ID.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub stripe_id: String,
|
pub stripe_id: String,
|
||||||
|
/// The grants associated with the user's account.
|
||||||
|
#[serde(default)]
|
||||||
|
pub grants: Vec<AuthGrant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type UserConnections =
|
pub type UserConnections =
|
||||||
|
@ -251,6 +254,7 @@ impl User {
|
||||||
request_count: 0,
|
request_count: 0,
|
||||||
connections: HashMap::new(),
|
connections: HashMap::new(),
|
||||||
stripe_id: String::new(),
|
stripe_id: String::new(),
|
||||||
|
grants: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,21 +3,63 @@ use serde::{Serialize, Deserialize};
|
||||||
use tetratto_shared::hash::hash;
|
use tetratto_shared::hash::hash;
|
||||||
use super::{Result, Error};
|
use super::{Result, Error};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
#[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<AppScope>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub enum PkceChallengeMethod {
|
pub enum PkceChallengeMethod {
|
||||||
S256,
|
S256,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub enum AppScope {
|
pub enum AppScope {
|
||||||
|
/// Read the user's profile (username, bio, etc).
|
||||||
UserReadProfile,
|
UserReadProfile,
|
||||||
|
/// Read the user's settings.
|
||||||
|
UserReadSettings,
|
||||||
|
/// Read the user's sessions and info.
|
||||||
UserReadSessions,
|
UserReadSessions,
|
||||||
|
/// Read posts as the user.
|
||||||
UserReadPosts,
|
UserReadPosts,
|
||||||
|
/// Read messages as the user.
|
||||||
UserReadMessages,
|
UserReadMessages,
|
||||||
|
/// Create posts as the user.
|
||||||
UserCreatePosts,
|
UserCreatePosts,
|
||||||
|
/// Create messages as the user.
|
||||||
UserCreateMessages,
|
UserCreateMessages,
|
||||||
|
/// Delete posts owned by the user.
|
||||||
UserDeletePosts,
|
UserDeletePosts,
|
||||||
|
/// Delete messages owned by the user.
|
||||||
UserDeleteMessages,
|
UserDeleteMessages,
|
||||||
|
/// Manage stacks owned by the user.
|
||||||
|
UserManageStacks,
|
||||||
|
/// Manage the user's following/unfollowing.
|
||||||
|
UserManageRelationships,
|
||||||
|
/// Manage the user's settings.
|
||||||
|
UserManageSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppScope {
|
impl AppScope {
|
||||||
|
@ -27,6 +69,7 @@ impl AppScope {
|
||||||
for scope in input.split(" ") {
|
for scope in input.split(" ") {
|
||||||
out.push(match scope {
|
out.push(match scope {
|
||||||
"user-read-profile" => Self::UserReadProfile,
|
"user-read-profile" => Self::UserReadProfile,
|
||||||
|
"user-read-settings" => Self::UserReadSettings,
|
||||||
"user-read-sessions" => Self::UserReadSessions,
|
"user-read-sessions" => Self::UserReadSessions,
|
||||||
"user-read-posts" => Self::UserReadPosts,
|
"user-read-posts" => Self::UserReadPosts,
|
||||||
"user-read-messages" => Self::UserReadMessages,
|
"user-read-messages" => Self::UserReadMessages,
|
||||||
|
@ -34,6 +77,9 @@ impl AppScope {
|
||||||
"user-create-messages" => Self::UserCreateMessages,
|
"user-create-messages" => Self::UserCreateMessages,
|
||||||
"user-delete-posts" => Self::UserDeletePosts,
|
"user-delete-posts" => Self::UserDeletePosts,
|
||||||
"user-delete-messages" => Self::UserDeleteMessages,
|
"user-delete-messages" => Self::UserDeleteMessages,
|
||||||
|
"user-manage-stacks" => Self::UserManageStacks,
|
||||||
|
"user-manage-relationships" => Self::UserManageRelationships,
|
||||||
|
"user-manage-settings" => Self::UserManageSettings,
|
||||||
_ => continue,
|
_ => continue,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
2
sql_changes/users_grants.sql
Normal file
2
sql_changes/users_grants.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN grants TEXT NOT NULL DEFAULT '[]';
|
Loading…
Add table
Add a link
Reference in a new issue