RFC7591 OAuth dynamic client registration + OpenID Connect Dynamic Client Registration (closes #136 closes #4)
This commit is contained in:
@@ -37,7 +37,10 @@ use crate::{
|
||||
api::management::enterprise::telemetry::TelemetryApi,
|
||||
auth::{
|
||||
authenticate::{Authenticator, HttpHeaders},
|
||||
oauth::{auth::OAuthApiHandler, openid::OpenIdHandler, token::TokenHandler, FormData},
|
||||
oauth::{
|
||||
auth::OAuthApiHandler, openid::OpenIdHandler, registration::ClientRegistrationHandler,
|
||||
token::TokenHandler, FormData,
|
||||
},
|
||||
rate_limit::RateLimiter,
|
||||
},
|
||||
blob::{download::BlobDownload, upload::BlobUpload, DownloadResponse, UploadResponse},
|
||||
@@ -99,7 +102,7 @@ impl ParseHttp for Server {
|
||||
("", &Method::POST) => {
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
self.authenticate_headers(&req, &session).await?;
|
||||
self.authenticate_headers(&req, &session, false).await?;
|
||||
|
||||
let request = fetch_body(
|
||||
&mut req,
|
||||
@@ -128,7 +131,7 @@ impl ParseHttp for Server {
|
||||
("download", &Method::GET) => {
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
self.authenticate_headers(&req, &session).await?;
|
||||
self.authenticate_headers(&req, &session, false).await?;
|
||||
|
||||
if let (Some(_), Some(blob_id), Some(name)) = (
|
||||
path.next().and_then(|p| Id::from_bytes(p.as_bytes())),
|
||||
@@ -157,7 +160,7 @@ impl ParseHttp for Server {
|
||||
("upload", &Method::POST) => {
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
self.authenticate_headers(&req, &session).await?;
|
||||
self.authenticate_headers(&req, &session, false).await?;
|
||||
|
||||
if let Some(account_id) =
|
||||
path.next().and_then(|p| Id::from_bytes(p.as_bytes()))
|
||||
@@ -192,14 +195,14 @@ impl ParseHttp for Server {
|
||||
("eventsource", &Method::GET) => {
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
self.authenticate_headers(&req, &session).await?;
|
||||
self.authenticate_headers(&req, &session, false).await?;
|
||||
|
||||
return self.handle_event_source(req, access_token).await;
|
||||
}
|
||||
("ws", &Method::GET) => {
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
self.authenticate_headers(&req, &session).await?;
|
||||
self.authenticate_headers(&req, &session, false).await?;
|
||||
|
||||
return self
|
||||
.upgrade_websocket_connection(req, access_token, session)
|
||||
@@ -215,7 +218,7 @@ impl ParseHttp for Server {
|
||||
("jmap", &Method::GET) => {
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
self.authenticate_headers(&req, &session).await?;
|
||||
self.authenticate_headers(&req, &session, false).await?;
|
||||
|
||||
return self
|
||||
.handle_session_resource(ctx.resolve_response_url(self).await, access_token)
|
||||
@@ -286,7 +289,7 @@ impl ParseHttp for Server {
|
||||
("introspect", &Method::POST) => {
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
self.authenticate_headers(&req, &session).await?;
|
||||
self.authenticate_headers(&req, &session, false).await?;
|
||||
|
||||
return self
|
||||
.handle_token_introspect(&mut req, &access_token, session.session_id)
|
||||
@@ -295,10 +298,15 @@ impl ParseHttp for Server {
|
||||
("userinfo", &Method::GET) => {
|
||||
// Authenticate request
|
||||
let (_in_flight, access_token) =
|
||||
self.authenticate_headers(&req, &session).await?;
|
||||
self.authenticate_headers(&req, &session, false).await?;
|
||||
|
||||
return self.handle_userinfo_request(&access_token).await;
|
||||
}
|
||||
("register", &Method::POST) => {
|
||||
return self
|
||||
.handle_oauth_registration_request(&mut req, session)
|
||||
.await;
|
||||
}
|
||||
("jwks.json", &Method::GET) => {
|
||||
// Limit anonymous requests
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
@@ -317,11 +325,10 @@ impl ParseHttp for Server {
|
||||
}
|
||||
|
||||
// Authenticate user
|
||||
match self.authenticate_headers(&req, &session).await {
|
||||
match self.authenticate_headers(&req, &session, true).await {
|
||||
Ok((_, access_token)) => {
|
||||
let body = fetch_body(&mut req, 1024 * 1024, session.session_id).await;
|
||||
return self
|
||||
.handle_api_manage_request(&req, body, access_token, &session)
|
||||
.handle_api_manage_request(&mut req, access_token, &session)
|
||||
.await;
|
||||
}
|
||||
Err(err) => {
|
||||
|
||||
@@ -39,7 +39,10 @@ use stores::ManageStore;
|
||||
|
||||
use crate::{auth::oauth::auth::OAuthApiHandler, email::crypto::CryptoHandler};
|
||||
|
||||
use super::{http::HttpSessionData, HttpRequest, HttpResponse};
|
||||
use super::{
|
||||
http::{fetch_body, HttpSessionData},
|
||||
HttpRequest, HttpResponse,
|
||||
};
|
||||
use std::future::Future;
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -69,8 +72,7 @@ pub enum ManagementApiError<'x> {
|
||||
pub trait ManagementApi: Sync + Send {
|
||||
fn handle_api_manage_request(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
body: Option<Vec<u8>>,
|
||||
req: &mut HttpRequest,
|
||||
access_token: Arc<AccessToken>,
|
||||
session: &HttpSessionData,
|
||||
) -> impl Future<Output = trc::Result<HttpResponse>> + Send;
|
||||
@@ -80,11 +82,11 @@ impl ManagementApi for Server {
|
||||
#[allow(unused_variables)]
|
||||
async fn handle_api_manage_request(
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
body: Option<Vec<u8>>,
|
||||
req: &mut HttpRequest,
|
||||
access_token: Arc<AccessToken>,
|
||||
session: &HttpSessionData,
|
||||
) -> trc::Result<HttpResponse> {
|
||||
let body = fetch_body(req, 1024 * 1024, session.session_id).await;
|
||||
let path = req.uri().path().split('/').skip(2).collect::<Vec<_>>();
|
||||
|
||||
match path.first().copied().unwrap_or_default() {
|
||||
|
||||
@@ -95,6 +95,8 @@ impl PrincipalManager for Server {
|
||||
Type::Domain => Permission::DomainCreate,
|
||||
Type::Tenant => Permission::TenantCreate,
|
||||
Type::Role => Permission::RoleCreate,
|
||||
Type::ApiKey => Permission::ApiKeyCreate,
|
||||
Type::OauthClient => Permission::OauthClientCreate,
|
||||
Type::Resource | Type::Location | Type::Other => Permission::PrincipalCreate,
|
||||
})?;
|
||||
|
||||
@@ -175,6 +177,8 @@ impl PrincipalManager for Server {
|
||||
Type::Tenant,
|
||||
Type::Role,
|
||||
Type::Other,
|
||||
Type::ApiKey,
|
||||
Type::OauthClient,
|
||||
]
|
||||
};
|
||||
for typ in validate_types {
|
||||
@@ -185,6 +189,8 @@ impl PrincipalManager for Server {
|
||||
Type::Domain => Permission::DomainList,
|
||||
Type::Tenant => Permission::TenantList,
|
||||
Type::Role => Permission::RoleList,
|
||||
Type::ApiKey => Permission::ApiKeyList,
|
||||
Type::OauthClient => Permission::OauthClientList,
|
||||
Type::Resource | Type::Location | Type::Other => Permission::PrincipalList,
|
||||
})?;
|
||||
}
|
||||
@@ -266,6 +272,8 @@ impl PrincipalManager for Server {
|
||||
Type::Domain => Permission::DomainGet,
|
||||
Type::Tenant => Permission::TenantGet,
|
||||
Type::Role => Permission::RoleGet,
|
||||
Type::ApiKey => Permission::ApiKeyGet,
|
||||
Type::OauthClient => Permission::OauthClientGet,
|
||||
Type::Resource | Type::Location | Type::Other => {
|
||||
Permission::PrincipalGet
|
||||
}
|
||||
@@ -301,6 +309,8 @@ impl PrincipalManager for Server {
|
||||
Type::Domain => Permission::DomainDelete,
|
||||
Type::Tenant => Permission::TenantDelete,
|
||||
Type::Role => Permission::RoleDelete,
|
||||
Type::ApiKey => Permission::ApiKeyDelete,
|
||||
Type::OauthClient => Permission::OauthClientDelete,
|
||||
Type::Resource | Type::Location | Type::Other => {
|
||||
Permission::PrincipalDelete
|
||||
}
|
||||
@@ -347,6 +357,8 @@ impl PrincipalManager for Server {
|
||||
Type::Domain => Permission::DomainUpdate,
|
||||
Type::Tenant => Permission::TenantUpdate,
|
||||
Type::Role => Permission::RoleUpdate,
|
||||
Type::ApiKey => Permission::ApiKeyUpdate,
|
||||
Type::OauthClient => Permission::OauthClientUpdate,
|
||||
Type::Resource | Type::Location | Type::Other => {
|
||||
Permission::PrincipalUpdate
|
||||
}
|
||||
@@ -382,7 +394,8 @@ impl PrincipalManager for Server {
|
||||
| PrincipalField::Picture
|
||||
| PrincipalField::MemberOf
|
||||
| PrincipalField::Members
|
||||
| PrincipalField::Lists => (),
|
||||
| PrincipalField::Lists
|
||||
| PrincipalField::Urls => (),
|
||||
PrincipalField::Tenant => {
|
||||
// Tenants are not allowed to change their tenantId
|
||||
if access_token.tenant.is_some() {
|
||||
|
||||
@@ -24,6 +24,7 @@ pub trait Authenticator: Sync + Send {
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
session: &HttpSessionData,
|
||||
allow_api_access: bool,
|
||||
) -> impl Future<Output = trc::Result<(InFlight, Arc<AccessToken>)>> + Send;
|
||||
}
|
||||
|
||||
@@ -32,6 +33,7 @@ impl Authenticator for Server {
|
||||
&self,
|
||||
req: &HttpRequest,
|
||||
session: &HttpSessionData,
|
||||
allow_api_access: bool,
|
||||
) -> trc::Result<(InFlight, Arc<AccessToken>)> {
|
||||
if let Some((mechanism, token)) = req.authorization() {
|
||||
let access_token =
|
||||
@@ -43,29 +45,24 @@ impl Authenticator for Server {
|
||||
self.is_auth_allowed_soft(&session.remote_ip).await?;
|
||||
|
||||
// Decode the base64 encoded credentials
|
||||
if let Some((username, secret)) = base64_decode(token.as_bytes())
|
||||
.and_then(|token| String::from_utf8(token).ok())
|
||||
.and_then(|token| {
|
||||
token.split_once(':').map(|(login, secret)| {
|
||||
(login.trim().to_lowercase(), secret.to_string())
|
||||
})
|
||||
})
|
||||
{
|
||||
Credentials::Plain { username, secret }
|
||||
} else {
|
||||
return Err(trc::AuthEvent::Error
|
||||
decode_plain_auth(token).ok_or_else(|| {
|
||||
trc::AuthEvent::Error
|
||||
.into_err()
|
||||
.details("Failed to decode Basic auth request.")
|
||||
.id(token.to_string())
|
||||
.caused_by(trc::location!()));
|
||||
}
|
||||
.caused_by(trc::location!())
|
||||
})?
|
||||
} else if mechanism.eq_ignore_ascii_case("bearer") {
|
||||
// Enforce anonymous rate limit
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
|
||||
Credentials::OAuthBearer {
|
||||
token: token.to_string(),
|
||||
}
|
||||
decode_bearer_token(token, allow_api_access).ok_or_else(|| {
|
||||
trc::AuthEvent::Error
|
||||
.into_err()
|
||||
.details("Failed to decode Bearer token.")
|
||||
.id(token.to_string())
|
||||
.caused_by(trc::location!())
|
||||
})?
|
||||
} else {
|
||||
// Enforce anonymous rate limit
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
@@ -139,3 +136,28 @@ impl HttpHeaders for HttpRequest {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_plain_auth(token: &str) -> Option<Credentials<String>> {
|
||||
base64_decode(token.as_bytes())
|
||||
.and_then(|token| String::from_utf8(token).ok())
|
||||
.and_then(|token| {
|
||||
token
|
||||
.split_once(':')
|
||||
.map(|(login, secret)| Credentials::Plain {
|
||||
username: login.trim().to_lowercase(),
|
||||
secret: secret.to_string(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn decode_bearer_token(token: &str, allow_api_access: bool) -> Option<Credentials<String>> {
|
||||
if allow_api_access {
|
||||
if let Some(token) = token.strip_prefix("api_") {
|
||||
return decode_plain_auth(token);
|
||||
}
|
||||
}
|
||||
|
||||
Some(Credentials::OAuthBearer {
|
||||
token: token.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ pub struct OAuthMetadata {
|
||||
pub token_endpoint: String,
|
||||
pub authorization_endpoint: String,
|
||||
pub device_authorization_endpoint: String,
|
||||
pub registration_endpoint: String,
|
||||
pub introspection_endpoint: String,
|
||||
pub grant_types_supported: Vec<String>,
|
||||
pub response_types_supported: Vec<String>,
|
||||
@@ -191,7 +192,7 @@ impl OAuthApiHandler for Server {
|
||||
let client_id = FormData::from_request(req, MAX_POST_LEN, session.session_id)
|
||||
.await?
|
||||
.remove("client_id")
|
||||
.filter(|client_id| client_id.len() < CLIENT_ID_MAX_LEN)
|
||||
.filter(|client_id| client_id.len() <= CLIENT_ID_MAX_LEN)
|
||||
.ok_or_else(|| {
|
||||
trc::ResourceEvent::BadParameters
|
||||
.into_err()
|
||||
@@ -277,12 +278,14 @@ impl OAuthApiHandler for Server {
|
||||
Ok(JsonResponse::new(OAuthMetadata {
|
||||
authorization_endpoint: format!("{base_url}/authorize/code",),
|
||||
token_endpoint: format!("{base_url}/auth/token"),
|
||||
device_authorization_endpoint: format!("{base_url}/auth/device"),
|
||||
introspection_endpoint: format!("{base_url}/auth/introspect"),
|
||||
registration_endpoint: format!("{base_url}/auth/register"),
|
||||
grant_types_supported: vec![
|
||||
"authorization_code".to_string(),
|
||||
"implicit".to_string(),
|
||||
"urn:ietf:params:oauth:grant-type:device_code".to_string(),
|
||||
],
|
||||
device_authorization_endpoint: format!("{base_url}/auth/device"),
|
||||
response_types_supported: vec![
|
||||
"code".to_string(),
|
||||
"id_token".to_string(),
|
||||
@@ -290,7 +293,6 @@ impl OAuthApiHandler for Server {
|
||||
"id_token token".to_string(),
|
||||
],
|
||||
scopes_supported: vec!["openid".to_string(), "offline_access".to_string()],
|
||||
introspection_endpoint: format!("{base_url}/auth/introspect"),
|
||||
issuer: base_url,
|
||||
})
|
||||
.into_http_response())
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::api::{http::fetch_body, HttpRequest};
|
||||
|
||||
pub mod auth;
|
||||
pub mod openid;
|
||||
pub mod registration;
|
||||
pub mod token;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
|
||||
156
crates/jmap/src/auth/oauth/registration.rs
Normal file
156
crates/jmap/src/auth/oauth/registration.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::future::Future;
|
||||
|
||||
use common::{
|
||||
auth::oauth::registration::{ClientRegistrationRequest, ClientRegistrationResponse},
|
||||
Server,
|
||||
};
|
||||
use directory::{
|
||||
backend::internal::{lookup::DirectoryStore, manage::ManageDirectory, PrincipalField},
|
||||
Permission, Principal, QueryBy, Type,
|
||||
};
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use trc::{AddContext, AuthEvent};
|
||||
|
||||
use crate::{
|
||||
api::{
|
||||
http::{fetch_body, HttpSessionData, ToHttpResponse},
|
||||
HttpRequest, HttpResponse, JsonResponse,
|
||||
},
|
||||
auth::{authenticate::Authenticator, rate_limit::RateLimiter},
|
||||
};
|
||||
|
||||
use super::ErrorType;
|
||||
|
||||
pub trait ClientRegistrationHandler: Sync + Send {
|
||||
fn handle_oauth_registration_request(
|
||||
&self,
|
||||
req: &mut HttpRequest,
|
||||
session: HttpSessionData,
|
||||
) -> impl Future<Output = trc::Result<HttpResponse>> + Send;
|
||||
|
||||
fn validate_client_registration(
|
||||
&self,
|
||||
client_id: &str,
|
||||
redirect_uri: Option<&str>,
|
||||
account_id: u32,
|
||||
) -> impl Future<Output = trc::Result<Option<ErrorType>>> + Send;
|
||||
}
|
||||
impl ClientRegistrationHandler for Server {
|
||||
async fn handle_oauth_registration_request(
|
||||
&self,
|
||||
req: &mut HttpRequest,
|
||||
session: HttpSessionData,
|
||||
) -> trc::Result<HttpResponse> {
|
||||
if !self.core.oauth.allow_anonymous_client_registration {
|
||||
// Authenticate request
|
||||
let (_, access_token) = self.authenticate_headers(req, &session, true).await?;
|
||||
|
||||
// Validate permissions
|
||||
access_token.assert_has_permission(Permission::OauthClientRegistration)?;
|
||||
} else {
|
||||
self.is_anonymous_allowed(&session.remote_ip).await?;
|
||||
}
|
||||
|
||||
// Parse request
|
||||
let body = fetch_body(req, 20 * 1024, session.session_id).await;
|
||||
let request = serde_json::from_slice::<ClientRegistrationRequest>(
|
||||
body.as_deref().unwrap_or_default(),
|
||||
)
|
||||
.map_err(|err| {
|
||||
trc::EventType::Resource(trc::ResourceEvent::BadParameters).from_json_error(err)
|
||||
})?;
|
||||
|
||||
// Generate client ID
|
||||
let client_id = thread_rng()
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(20)
|
||||
.map(|ch| char::from(ch.to_ascii_lowercase()))
|
||||
.collect::<String>();
|
||||
self.store()
|
||||
.create_principal(
|
||||
Principal::new(u32::MAX, Type::OauthClient)
|
||||
.with_field(PrincipalField::Name, client_id.clone())
|
||||
.with_field(PrincipalField::Urls, request.redirect_uris.clone())
|
||||
.with_opt_field(PrincipalField::Description, request.client_name.clone())
|
||||
.with_field(PrincipalField::Emails, request.contacts.clone())
|
||||
.with_opt_field(PrincipalField::Picture, request.logo_uri.clone()),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
trc::event!(
|
||||
Auth(AuthEvent::ClientRegistration),
|
||||
Id = client_id.to_string(),
|
||||
RemoteIp = session.remote_ip
|
||||
);
|
||||
|
||||
Ok(JsonResponse::new(ClientRegistrationResponse {
|
||||
client_id,
|
||||
request,
|
||||
..Default::default()
|
||||
})
|
||||
.into_http_response())
|
||||
}
|
||||
|
||||
async fn validate_client_registration(
|
||||
&self,
|
||||
client_id: &str,
|
||||
redirect_uri: Option<&str>,
|
||||
account_id: u32,
|
||||
) -> trc::Result<Option<ErrorType>> {
|
||||
if !self.core.oauth.require_client_authentication {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Fetch client registration
|
||||
let found_registration = if let Some(client) = self
|
||||
.store()
|
||||
.query(QueryBy::Name(client_id), false)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.filter(|p| p.typ() == Type::OauthClient)
|
||||
{
|
||||
if let Some(redirect_uri) = redirect_uri {
|
||||
if client
|
||||
.get_str_array(PrincipalField::Urls)
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.any(|uri| uri == redirect_uri)
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
} else {
|
||||
// Device flow does not require a redirect URI
|
||||
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Check if the account is allowed to override client registration
|
||||
if self
|
||||
.get_cached_access_token(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.has_permission(Permission::OauthClientOverride)
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some(if found_registration {
|
||||
ErrorType::InvalidClient
|
||||
} else {
|
||||
ErrorType::InvalidRequest
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,8 @@ use crate::api::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
ErrorType, FormData, OAuthCode, OAuthResponse, OAuthStatus, TokenResponse, MAX_POST_LEN,
|
||||
registration::ClientRegistrationHandler, ErrorType, FormData, OAuthCode, OAuthResponse,
|
||||
OAuthStatus, TokenResponse, MAX_POST_LEN,
|
||||
};
|
||||
|
||||
pub trait TokenHandler: Sync + Send {
|
||||
@@ -80,23 +81,35 @@ impl TokenHandler for Server {
|
||||
if client_id != oauth.client_id || redirect_uri != oauth.params {
|
||||
TokenResponse::error(ErrorType::InvalidClient)
|
||||
} else if oauth.status == OAuthStatus::Authorized {
|
||||
// Mark this token as issued
|
||||
self.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_delete(format!("oauth:{code}").into_bytes())
|
||||
.await?;
|
||||
// Validate client id
|
||||
if let Some(error) = self
|
||||
.validate_client_registration(
|
||||
client_id,
|
||||
redirect_uri.into(),
|
||||
oauth.account_id,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
TokenResponse::error(error)
|
||||
} else {
|
||||
// Mark this token as issued
|
||||
self.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_delete(format!("oauth:{code}").into_bytes())
|
||||
.await?;
|
||||
|
||||
// Issue token
|
||||
self.issue_token(oauth.account_id, &oauth.client_id, issuer, true)
|
||||
.await
|
||||
.map(TokenResponse::Granted)
|
||||
.map_err(|err| {
|
||||
trc::AuthEvent::Error
|
||||
.into_err()
|
||||
.details(err)
|
||||
.caused_by(trc::location!())
|
||||
})?
|
||||
// Issue token
|
||||
self.issue_token(oauth.account_id, &oauth.client_id, issuer, true)
|
||||
.await
|
||||
.map(TokenResponse::Granted)
|
||||
.map_err(|err| {
|
||||
trc::AuthEvent::Error
|
||||
.into_err()
|
||||
.details(err)
|
||||
.caused_by(trc::location!())
|
||||
})?
|
||||
}
|
||||
} else {
|
||||
TokenResponse::error(ErrorType::InvalidGrant)
|
||||
}
|
||||
@@ -126,15 +139,26 @@ impl TokenHandler for Server {
|
||||
} else {
|
||||
match oauth.status {
|
||||
OAuthStatus::Authorized => {
|
||||
// Mark this token as issued
|
||||
self.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_delete(format!("oauth:{device_code}").into_bytes())
|
||||
.await?;
|
||||
if let Some(error) = self
|
||||
.validate_client_registration(client_id, None, oauth.account_id)
|
||||
.await?
|
||||
{
|
||||
TokenResponse::error(error)
|
||||
} else {
|
||||
// Mark this token as issued
|
||||
self.core
|
||||
.storage
|
||||
.lookup
|
||||
.key_delete(format!("oauth:{device_code}").into_bytes())
|
||||
.await?;
|
||||
|
||||
// Issue token
|
||||
self.issue_token(oauth.account_id, &oauth.client_id, issuer, true)
|
||||
// Issue token
|
||||
self.issue_token(
|
||||
oauth.account_id,
|
||||
&oauth.client_id,
|
||||
issuer,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.map(TokenResponse::Granted)
|
||||
.map_err(|err| {
|
||||
@@ -143,6 +167,7 @@ impl TokenHandler for Server {
|
||||
.details(err)
|
||||
.caused_by(trc::location!())
|
||||
})?
|
||||
}
|
||||
}
|
||||
OAuthStatus::Pending => {
|
||||
TokenResponse::error(ErrorType::AuthorizationPending)
|
||||
|
||||
Reference in New Issue
Block a user