alibi_plugins/oauth_provider/
revocation.rs1use super::OAuthProviderPlugin;
3use alibi_core::endpoint::{EndpointResponse, ServerEndpoint};
4use alibi_core::session::SessionRequest;
5use alibi_core::store::OAuthTokenKind;
6use alibi_core::utils::json::JsValue;
7use alibi_core::{AuthContext, AuthResult, AuthSchema};
8use serde_json::{Value, json};
9impl OAuthProviderPlugin {
10 pub fn oauth2_revoke_endpoint(
12 input: &impl serde::Serialize,
13 ) -> AuthResult<ServerEndpoint<Value>> {
14 ServerEndpoint::new("oauth-provider", "oauth2Revoke").with_body(input)
15 }
16 pub(super) async fn revoke<S: AuthSchema>(
17 &self,
18 input: Option<&JsValue>,
19 req: &impl SessionRequest,
20 ctx: &AuthContext<S>,
21 ) -> AuthResult<EndpointResponse> {
22 let body = input
23 .map(JsValue::to_json_value)
24 .transpose()?
25 .unwrap_or_else(|| json!({}));
26 for (field, required) in [
27 ("client_id", false),
28 ("client_secret", false),
29 ("client_assertion", false),
30 ("client_assertion_type", false),
31 ("token", true),
32 ("token_type_hint", false),
33 ] {
34 if let Some(value) = body.get(field) {
35 if !value.is_string() {
36 return error(
37 400,
38 "invalid_request",
39 &format!(
40 "{field} {}",
41 if value.is_array() {
42 "must not appear more than once"
43 } else {
44 "must be a string"
45 }
46 ),
47 );
48 }
49 } else if required {
50 return error(400, "invalid_request", &format!("{field} is required"));
51 }
52 }
53 let credentials = match super::credentials::extract(&body, req, ctx, self, "revoke").await?
54 {
55 Ok(credentials) => credentials,
56 Err(response) => return Ok(response),
57 };
58 if credentials.client_id.is_empty() {
59 return error(401, "invalid_client", "missing required credentials");
60 }
61 let raw = body
62 .get("token")
63 .and_then(Value::as_str)
64 .unwrap_or_default();
65 let token = raw
66 .strip_prefix("Bearer ")
67 .or_else(|| raw.strip_prefix("DPoP "))
68 .unwrap_or(raw);
69 if token.is_empty() {
70 return error(
71 400,
72 "invalid_request",
73 "missing a required token for introspection",
74 );
75 }
76 if let Err(response) = credentials.authenticate(ctx, self).await? {
77 return Ok(response);
78 }
79 let hint = body
80 .get("token_type_hint")
81 .and_then(Value::as_str)
82 .unwrap_or_default();
83 let kinds: &[OAuthTokenKind] = match hint {
84 "access_token" => &[OAuthTokenKind::Access],
85 "refresh_token" => &[OAuthTokenKind::Refresh],
86 _ => &[OAuthTokenKind::Access, OAuthTokenKind::Refresh],
87 };
88 for kind in kinds {
89 let hash = self
90 .hash_token(
91 token,
92 if matches!(kind, OAuthTokenKind::Refresh) {
93 "refresh_token"
94 } else {
95 "access_token"
96 },
97 )
98 .await?;
99 let Some(row) = ctx.database.get_oauth_token(*kind, &hash).await? else {
100 continue;
101 };
102 if matches!(kind, OAuthTokenKind::Refresh)
103 && row.metadata.get("revoked").is_some_and(|v| !v.is_null())
104 {
105 ctx.database
106 .delete_oauth_refresh_family(
107 &credentials.client_id,
108 row.user_id.as_deref().unwrap_or("undefined"),
109 )
110 .await?;
111 return error(400, "invalid_request", "refresh token revoked");
112 }
113 if row.client_id != credentials.client_id {
114 return EndpointResponse::json(&Value::Null);
115 }
116 if matches!(kind, OAuthTokenKind::Access) {
117 ctx.database.delete_oauth_token(*kind, &hash).await?;
118 } else {
119 let now = chrono::DateTime::from_timestamp(chrono::Utc::now().timestamp(), 0)
120 .ok_or_else(|| alibi_core::AuthError::internal("Invalid revocation time"))?;
121 if !ctx.database.revoke_oauth_refresh_token(&hash, now).await? {
122 ctx.database
123 .delete_oauth_refresh_family(
124 &credentials.client_id,
125 row.user_id.as_deref().unwrap_or("undefined"),
126 )
127 .await?;
128 return error(400, "invalid_request", "refresh token revoked");
129 }
130 ctx.database
131 .delete_oauth_access_tokens_for_refresh(&row.id)
132 .await?;
133 }
134 return Ok(EndpointResponse::empty().with_header("Content-Type", "application/json"));
135 }
136 error(
137 400,
138 "invalid_request",
139 match hint {
140 "access_token" => "Invalid access token",
141 _ => "token not found",
142 },
143 )
144 }
145}
146fn error(status: u16, code: &str, description: &str) -> AuthResult<EndpointResponse> {
147 super::clients::status_protocol_error(status, code, description)
148}