Skip to main content

headless_lms_server/controllers/main_frontend/oauth/
grants.rs

1use crate::domain::exercise_services::token::invalidate_cached_users;
2use crate::prelude::*;
3use actix_web::{HttpResponse, web};
4use headless_lms_base::config::ApplicationConfiguration;
5use headless_lms_utils::cache::Cache;
6use models::oauth_user_client_scopes::{OAuthGrant, OAuthUserClientScopes};
7use sqlx::PgPool;
8use uuid::Uuid;
9
10/**
11GET `/api/v0/main-frontend/oauth/grants` - The signed-in user's OAuth grants, including AI agent
12clients, with their scopes and when they were last used.
13*/
14#[instrument(skip(pool, auth_user))]
15#[utoipa::path(
16    get,
17    path = "/grants",
18    operation_id = "getOauthGrants",
19    tag = "oauth",
20    responses(
21        (status = 200, description = "The user's grants", body = [OAuthGrant])
22    )
23)]
24pub async fn get_grants(
25    pool: web::Data<PgPool>,
26    auth_user: AuthUser,
27) -> ControllerResult<web::Json<Vec<OAuthGrant>>> {
28    let mut conn = pool.acquire().await?;
29    let token = skip_authorize();
30    let grants = OAuthUserClientScopes::get_grants_for_user(&mut conn, auth_user.id).await?;
31    token.authorized_ok(web::Json(grants))
32}
33
34/**
35DELETE `/api/v0/main-frontend/oauth/grants/{grant_id}` - Revokes one of the signed-in user's grants
36and every token issued from it. The client has to ask for consent again.
37*/
38#[instrument(skip(pool, auth_user, app_conf, cache))]
39#[utoipa::path(
40    delete,
41    path = "/grants/{grant_id}",
42    operation_id = "revokeOauthGrant",
43    tag = "oauth",
44    params(
45        ("grant_id" = Uuid, Path, description = "Grant id")
46    ),
47    responses(
48        (status = 204, description = "Grant revoked")
49    )
50)]
51pub async fn revoke_grant(
52    pool: web::Data<PgPool>,
53    auth_user: AuthUser,
54    grant_id: web::Path<Uuid>,
55    app_conf: web::Data<ApplicationConfiguration>,
56    cache: web::Data<Cache>,
57) -> ControllerResult<HttpResponse> {
58    let mut conn = pool.acquire().await?;
59    let token = skip_authorize();
60    let revoked_digests =
61        OAuthUserClientScopes::revoke_grant_by_id(&mut conn, auth_user.id, *grant_id).await?;
62    // Without this the deleted tokens keep authenticating from cache for the rest of their TTL.
63    let token_hmac_key = &app_conf.oauth_server_configuration.oauth_token_hmac_key;
64    invalidate_cached_users(&cache, &revoked_digests, token_hmac_key).await;
65    token.authorized_ok(HttpResponse::NoContent().finish())
66}
67
68pub fn _add_routes(cfg: &mut web::ServiceConfig) {
69    cfg.route("/grants", web::get().to(get_grants))
70        .route("/grants/{grant_id}", web::delete().to(revoke_grant));
71}