api-tokens
This commit is contained in:
@@ -18,6 +18,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
auth::{
|
||||
api_tokens::{find_active_token_by_secret, touch_api_token},
|
||||
passkeys::{
|
||||
AuthenticationChallengeResponse, PasskeyLoginFinishPayload, PasskeyLoginStartPayload,
|
||||
PasskeyRegistrationFinishPayload, PasskeySummary, RegistrationChallengeResponse,
|
||||
@@ -26,7 +27,8 @@ use crate::{
|
||||
},
|
||||
error::{AppError, AppResult},
|
||||
models::{
|
||||
MagicToken, MagicTokenKind, NewRefreshToken, NewUser, RefreshToken, TenantStatus, User,
|
||||
ApiTokenCapability, MagicToken, MagicTokenKind, NewRefreshToken, NewUser, RefreshToken,
|
||||
TenantStatus, User,
|
||||
},
|
||||
schema::{
|
||||
magic_tokens::dsl as magic_dsl, refresh_tokens, tenants::dsl as tenant_dsl,
|
||||
@@ -59,6 +61,11 @@ pub struct LoginRequest {
|
||||
pub preferred_tenant_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct ApiTokenExchangeRequest {
|
||||
pub api_token: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, ToSchema)]
|
||||
pub struct LoginResponse {
|
||||
pub access_token: String,
|
||||
@@ -120,6 +127,7 @@ pub enum LoginResponseVariants {
|
||||
#[openapi(
|
||||
paths(
|
||||
login,
|
||||
api_token_exchange,
|
||||
signup_start,
|
||||
signup_finish,
|
||||
refresh,
|
||||
@@ -134,6 +142,7 @@ pub enum LoginResponseVariants {
|
||||
),
|
||||
components(schemas(
|
||||
LoginRequest,
|
||||
ApiTokenExchangeRequest,
|
||||
SignupStartRequest,
|
||||
SignupStartResponse,
|
||||
SignupFinishRequest,
|
||||
@@ -150,6 +159,7 @@ pub enum LoginResponseVariants {
|
||||
crate::auth::passkeys::PasskeyRegistrationFinishPayload,
|
||||
crate::auth::passkeys::PasskeyLoginStartPayload,
|
||||
crate::auth::passkeys::PasskeyLoginFinishPayload,
|
||||
crate::models::ApiTokenCapability,
|
||||
))
|
||||
)]
|
||||
pub struct AuthApiDoc;
|
||||
@@ -201,6 +211,69 @@ pub async fn login(
|
||||
)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/auth/exchange-api-token",
|
||||
request_body = ApiTokenExchangeRequest,
|
||||
responses((status = 200, description = "Access token issued", body = LoginResponse)),
|
||||
tag = "Auth"
|
||||
)]
|
||||
pub async fn api_token_exchange(
|
||||
State(state): State<AppState>,
|
||||
Json(payload): Json<ApiTokenExchangeRequest>,
|
||||
) -> AppResult<Json<LoginResponse>> {
|
||||
let secret = payload.api_token.trim();
|
||||
if secret.is_empty() {
|
||||
return Err(AppError::bad_request("api_token must not be empty"));
|
||||
}
|
||||
|
||||
let mut conn = state.db_unscoped()?;
|
||||
|
||||
let token = find_active_token_by_secret(&mut conn, None, secret, ApiTokenCapability::Api)?
|
||||
.ok_or_else(AppError::unauthorized)?;
|
||||
|
||||
let user: User = dsl::users.find(token.user_id).first(&mut conn)?;
|
||||
|
||||
apply_user_guc(&mut conn, user.id)?;
|
||||
let membership = memberships_dsl::user_memberships
|
||||
.filter(memberships_dsl::user_id.eq(user.id))
|
||||
.filter(memberships_dsl::tenant_id.eq(token.tenant_id))
|
||||
.select(memberships_dsl::tenant_id)
|
||||
.first::<Uuid>(&mut conn)
|
||||
.optional()?;
|
||||
clear_user_guc(&mut conn)?;
|
||||
|
||||
if membership.is_none() {
|
||||
return Err(AppError::unauthorized());
|
||||
}
|
||||
|
||||
apply_tenant_guc(&mut conn, token.tenant_id)?;
|
||||
touch_api_token(&mut conn, token.id)?;
|
||||
|
||||
let access_token = state
|
||||
.jwt
|
||||
.generate_token(user.id, token.tenant_id, &user.username)
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
let tenant_name: String = tenant_dsl::tenants
|
||||
.find(token.tenant_id)
|
||||
.select(tenant_dsl::name)
|
||||
.first(&mut conn)
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
let response = LoginResponse {
|
||||
access_token,
|
||||
token_type: "Bearer".to_string(),
|
||||
expires_in: state.config.jwt_expiry_minutes * 60,
|
||||
tenant: TenantSnippet {
|
||||
id: token.tenant_id,
|
||||
name: tenant_name,
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/auth/signup/start",
|
||||
|
||||
Reference in New Issue
Block a user