Spiegel von
https://github.com/dani-garcia/vaultwarden.git
synchronisiert 2024-11-05 02:28:00 +01:00
JWT Refresh Token
Dieser Commit ist enthalten in:
Ursprung
a4c7fadbf4
Commit
6d104c9ad5
4 geänderte Dateien mit 248 neuen und 108 gelöschten Zeilen
|
@ -3,6 +3,7 @@ use num_traits::FromPrimitive;
|
|||
use rocket::serde::json::Json;
|
||||
use rocket::{
|
||||
form::{Form, FromForm},
|
||||
http::Status,
|
||||
Route,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
@ -17,7 +18,8 @@ use crate::{
|
|||
push::register_push_device,
|
||||
ApiResult, EmptyResult, JsonResult,
|
||||
},
|
||||
auth::{generate_organization_api_key_login_claims, ClientHeaders, ClientIp},
|
||||
auth,
|
||||
auth::{generate_organization_api_key_login_claims, AuthMethod, AuthMethodScope, ClientHeaders, ClientIp},
|
||||
db::{models::*, DbConn},
|
||||
error::MapResult,
|
||||
mail, util, CONFIG,
|
||||
|
@ -96,43 +98,43 @@ async fn login(data: Form<ConnectData>, client_header: ClientHeaders, mut conn:
|
|||
|
||||
async fn _refresh_login(data: ConnectData, conn: &mut DbConn) -> JsonResult {
|
||||
// Extract token
|
||||
let token = data.refresh_token.unwrap();
|
||||
let refresh_token = match data.refresh_token {
|
||||
Some(token) => token,
|
||||
None => err_code!("Missing refresh_token", Status::Unauthorized.code),
|
||||
};
|
||||
|
||||
// Get device by refresh token
|
||||
let mut device = Device::find_by_refresh_token(&token, conn).await.map_res("Invalid refresh token")?;
|
||||
|
||||
let scope = "api offline_access";
|
||||
let scope_vec = vec!["api".into(), "offline_access".into()];
|
||||
|
||||
// Common
|
||||
let user = User::find_by_uuid(&device.user_uuid, conn).await.unwrap();
|
||||
// ---
|
||||
// Disabled this variable, it was used to generate the JWT
|
||||
// Because this might get used in the future, and is add by the Bitwarden Server, lets keep it, but then commented out
|
||||
// See: https://github.com/dani-garcia/vaultwarden/issues/4156
|
||||
// ---
|
||||
// let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, conn).await;
|
||||
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec);
|
||||
device.save(conn).await?;
|
||||
match auth::refresh_tokens(&refresh_token, conn).await {
|
||||
Err(err) => err_code!(err.to_string(), Status::Unauthorized.code),
|
||||
Ok((mut device, user, auth_tokens)) => {
|
||||
// Save to update `device.updated_at` to track usage
|
||||
device.save(conn).await?;
|
||||
|
||||
let result = json!({
|
||||
"access_token": access_token,
|
||||
"expires_in": expires_in,
|
||||
"token_type": "Bearer",
|
||||
"refresh_token": device.refresh_token,
|
||||
"Key": user.akey,
|
||||
"PrivateKey": user.private_key,
|
||||
let result = json!({
|
||||
"refresh_token": auth_tokens.refresh_token(),
|
||||
"access_token": auth_tokens.access_token(),
|
||||
"expires_in": auth_tokens.expires_in(),
|
||||
"token_type": "Bearer",
|
||||
"Key": user.akey,
|
||||
"PrivateKey": user.private_key,
|
||||
|
||||
"Kdf": user.client_kdf_type,
|
||||
"KdfIterations": user.client_kdf_iter,
|
||||
"KdfMemory": user.client_kdf_memory,
|
||||
"KdfParallelism": user.client_kdf_parallelism,
|
||||
"ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing
|
||||
"scope": scope,
|
||||
"unofficialServer": true,
|
||||
});
|
||||
"Kdf": user.client_kdf_type,
|
||||
"KdfIterations": user.client_kdf_iter,
|
||||
"KdfMemory": user.client_kdf_memory,
|
||||
"KdfParallelism": user.client_kdf_parallelism,
|
||||
"ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing
|
||||
"scope": auth_tokens.scope(),
|
||||
"unofficialServer": true,
|
||||
});
|
||||
|
||||
Ok(Json(result))
|
||||
Ok(Json(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn _password_login(
|
||||
|
@ -142,11 +144,7 @@ async fn _password_login(
|
|||
ip: &ClientIp,
|
||||
) -> JsonResult {
|
||||
// Validate scope
|
||||
let scope = data.scope.as_ref().unwrap();
|
||||
if scope != "api offline_access" {
|
||||
err!("Scope not supported")
|
||||
}
|
||||
let scope_vec = vec!["api".into(), "offline_access".into()];
|
||||
AuthMethod::Password.check_scope(data.scope.as_ref())?;
|
||||
|
||||
// Ratelimit the login
|
||||
crate::ratelimit::check_limit_login(&ip.ip)?;
|
||||
|
@ -279,14 +277,14 @@ async fn _password_login(
|
|||
// See: https://github.com/dani-garcia/vaultwarden/issues/4156
|
||||
// ---
|
||||
// let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, conn).await;
|
||||
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec);
|
||||
let auth_tokens = auth::AuthTokens::new(&device, &user, AuthMethod::Password);
|
||||
device.save(conn).await?;
|
||||
|
||||
let mut result = json!({
|
||||
"access_token": access_token,
|
||||
"expires_in": expires_in,
|
||||
"access_token": auth_tokens.access_token(),
|
||||
"expires_in": auth_tokens.expires_in(),
|
||||
"token_type": "Bearer",
|
||||
"refresh_token": device.refresh_token,
|
||||
"refresh_token": auth_tokens.refresh_token(),
|
||||
"Key": user.akey,
|
||||
"PrivateKey": user.private_key,
|
||||
//"TwoFactorToken": "11122233333444555666777888999"
|
||||
|
@ -301,7 +299,7 @@ async fn _password_login(
|
|||
"object": "masterPasswordPolicy",
|
||||
},
|
||||
|
||||
"scope": scope,
|
||||
"scope": auth_tokens.scope(),
|
||||
"unofficialServer": true,
|
||||
"UserDecryptionOptions": {
|
||||
"HasMasterPassword": !user.password_hash.is_empty(),
|
||||
|
@ -327,9 +325,9 @@ async fn _api_key_login(
|
|||
crate::ratelimit::check_limit_login(&ip.ip)?;
|
||||
|
||||
// Validate scope
|
||||
match data.scope.as_ref().unwrap().as_ref() {
|
||||
"api" => _user_api_key_login(data, user_uuid, conn, ip).await,
|
||||
"api.organization" => _organization_api_key_login(data, conn, ip).await,
|
||||
match data.scope.as_ref() {
|
||||
Some(scope) if scope == &AuthMethod::UserApiKey.scope() => _user_api_key_login(data, user_uuid, conn, ip).await,
|
||||
Some(scope) if scope == &AuthMethod::OrgApiKey.scope() => _organization_api_key_login(data, conn, ip).await,
|
||||
_ => err!("Scope not supported"),
|
||||
}
|
||||
}
|
||||
|
@ -395,15 +393,15 @@ async fn _user_api_key_login(
|
|||
}
|
||||
}
|
||||
|
||||
// Common
|
||||
let scope_vec = vec!["api".into()];
|
||||
// ---
|
||||
// Disabled this variable, it was used to generate the JWT
|
||||
// Because this might get used in the future, and is add by the Bitwarden Server, lets keep it, but then commented out
|
||||
// See: https://github.com/dani-garcia/vaultwarden/issues/4156
|
||||
// ---
|
||||
// let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, conn).await;
|
||||
let (access_token, expires_in) = device.refresh_tokens(&user, scope_vec);
|
||||
let access_claims = auth::LoginJwtClaims::default(&device, &user, &auth::AuthMethod::UserApiKey);
|
||||
|
||||
// Save to update `device.updated_at` to track usage
|
||||
device.save(conn).await?;
|
||||
|
||||
info!("User {} logged in successfully via API key. IP: {}", user.email, ip.ip);
|
||||
|
@ -411,8 +409,8 @@ async fn _user_api_key_login(
|
|||
// Note: No refresh_token is returned. The CLI just repeats the
|
||||
// client_credentials login flow when the existing token expires.
|
||||
let result = json!({
|
||||
"access_token": access_token,
|
||||
"expires_in": expires_in,
|
||||
"access_token": access_claims.token(),
|
||||
"expires_in": access_claims.expires_in(),
|
||||
"token_type": "Bearer",
|
||||
"Key": user.akey,
|
||||
"PrivateKey": user.private_key,
|
||||
|
@ -422,7 +420,7 @@ async fn _user_api_key_login(
|
|||
"KdfMemory": user.client_kdf_memory,
|
||||
"KdfParallelism": user.client_kdf_parallelism,
|
||||
"ResetMasterPassword": false, // TODO: Same as above
|
||||
"scope": "api",
|
||||
"scope": auth::AuthMethod::UserApiKey.scope(),
|
||||
"unofficialServer": true,
|
||||
});
|
||||
|
||||
|
@ -454,7 +452,7 @@ async fn _organization_api_key_login(data: ConnectData, conn: &mut DbConn, ip: &
|
|||
"access_token": access_token,
|
||||
"expires_in": 3600,
|
||||
"token_type": "Bearer",
|
||||
"scope": "api.organization",
|
||||
"scope": auth::AuthMethod::OrgApiKey.scope(),
|
||||
"unofficialServer": true,
|
||||
})))
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ pub use crate::api::{
|
|||
use crate::db::{models::User, DbConn};
|
||||
|
||||
// Type aliases for API methods results
|
||||
type ApiResult<T> = Result<T, crate::error::Error>;
|
||||
pub type ApiResult<T> = Result<T, crate::error::Error>;
|
||||
pub type JsonResult = ApiResult<Json<Value>>;
|
||||
pub type EmptyResult = ApiResult<()>;
|
||||
|
||||
|
|
199
src/auth.rs
199
src/auth.rs
|
@ -9,11 +9,13 @@ use openssl::rsa::Rsa;
|
|||
use serde::de::DeserializeOwned;
|
||||
use serde::ser::Serialize;
|
||||
|
||||
use crate::api::ApiResult;
|
||||
use crate::{error::Error, CONFIG};
|
||||
|
||||
const JWT_ALGORITHM: Algorithm = Algorithm::RS256;
|
||||
|
||||
pub static DEFAULT_VALIDITY: Lazy<TimeDelta> = Lazy::new(|| TimeDelta::try_hours(2).unwrap());
|
||||
pub static DEFAULT_REFRESH_VALIDITY: Lazy<TimeDelta> = Lazy::new(|| TimeDelta::try_days(30).unwrap());
|
||||
pub static DEFAULT_ACCESS_VALIDITY: Lazy<TimeDelta> = Lazy::new(|| TimeDelta::try_hours(2).unwrap());
|
||||
static JWT_HEADER: Lazy<Header> = Lazy::new(|| Header::new(JWT_ALGORITHM));
|
||||
|
||||
pub static JWT_LOGIN_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|login", CONFIG.domain_origin()));
|
||||
|
@ -91,6 +93,10 @@ fn decode_jwt<T: DeserializeOwned>(token: &str, issuer: String) -> Result<T, Err
|
|||
}
|
||||
}
|
||||
|
||||
pub fn decode_refresh(token: &str) -> Result<RefreshJwtClaims, Error> {
|
||||
decode_jwt(token, JWT_LOGIN_ISSUER.to_string())
|
||||
}
|
||||
|
||||
pub fn decode_login(token: &str) -> Result<LoginJwtClaims, Error> {
|
||||
decode_jwt(token, JWT_LOGIN_ISSUER.to_string())
|
||||
}
|
||||
|
@ -164,6 +170,68 @@ pub struct LoginJwtClaims {
|
|||
pub amr: Vec<String>,
|
||||
}
|
||||
|
||||
impl LoginJwtClaims {
|
||||
pub fn new(device: &Device, user: &User, nbf: i64, exp: i64, scope: Vec<String>) -> Self {
|
||||
// ---
|
||||
// Disabled these keys to be added to the JWT since they could cause the JWT to get too large
|
||||
// Also These key/value pairs are not used anywhere by either Vaultwarden or Bitwarden Clients
|
||||
// Because these might get used in the future, and they are added by the Bitwarden Server, lets keep it, but then commented out
|
||||
// ---
|
||||
// fn arg: orgs: Vec<super::UserOrganization>,
|
||||
// ---
|
||||
// let orgowner: Vec<_> = orgs.iter().filter(|o| o.atype == 0).map(|o| o.org_uuid.clone()).collect();
|
||||
// let orgadmin: Vec<_> = orgs.iter().filter(|o| o.atype == 1).map(|o| o.org_uuid.clone()).collect();
|
||||
// let orguser: Vec<_> = orgs.iter().filter(|o| o.atype == 2).map(|o| o.org_uuid.clone()).collect();
|
||||
// let orgmanager: Vec<_> = orgs.iter().filter(|o| o.atype == 3).map(|o| o.org_uuid.clone()).collect();
|
||||
|
||||
// Create the JWT claims struct, to send to the client
|
||||
Self {
|
||||
nbf,
|
||||
exp,
|
||||
iss: JWT_LOGIN_ISSUER.to_string(),
|
||||
sub: user.uuid.clone(),
|
||||
premium: true,
|
||||
name: user.name.clone(),
|
||||
email: user.email.clone(),
|
||||
email_verified: !CONFIG.mail_enabled() || user.verified_at.is_some(),
|
||||
|
||||
// ---
|
||||
// Disabled these keys to be added to the JWT since they could cause the JWT to get too large
|
||||
// Also These key/value pairs are not used anywhere by either Vaultwarden or Bitwarden Clients
|
||||
// Because these might get used in the future, and they are added by the Bitwarden Server, lets keep it, but then commented out
|
||||
// See: https://github.com/dani-garcia/vaultwarden/issues/4156
|
||||
// ---
|
||||
// orgowner,
|
||||
// orgadmin,
|
||||
// orguser,
|
||||
// orgmanager,
|
||||
sstamp: user.security_stamp.clone(),
|
||||
device: device.uuid.clone(),
|
||||
scope,
|
||||
amr: vec!["Application".into()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default(device: &Device, user: &User, auth_method: &AuthMethod) -> Self {
|
||||
let time_now = Utc::now();
|
||||
Self::new(
|
||||
device,
|
||||
user,
|
||||
time_now.timestamp(),
|
||||
(time_now + *DEFAULT_ACCESS_VALIDITY).timestamp(),
|
||||
auth_method.scope_vec(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn token(&self) -> String {
|
||||
encode_jwt(&self)
|
||||
}
|
||||
|
||||
pub fn expires_in(&self) -> i64 {
|
||||
self.exp - Utc::now().timestamp()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct InviteJwtClaims {
|
||||
// Not before
|
||||
|
@ -864,3 +932,132 @@ impl<'r> FromRequest<'r> for WsAccessTokenHeader {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AuthMethod {
|
||||
OrgApiKey,
|
||||
Password,
|
||||
UserApiKey,
|
||||
}
|
||||
|
||||
pub trait AuthMethodScope {
|
||||
fn scope_vec(&self) -> Vec<String>;
|
||||
fn scope(&self) -> String;
|
||||
fn check_scope(&self, scope: Option<&String>) -> ApiResult<String>;
|
||||
}
|
||||
|
||||
impl AuthMethodScope for AuthMethod {
|
||||
fn scope(&self) -> String {
|
||||
match self {
|
||||
AuthMethod::OrgApiKey => "api.organization".to_string(),
|
||||
AuthMethod::Password => "api offline_access".to_string(),
|
||||
AuthMethod::UserApiKey => "api".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn scope_vec(&self) -> Vec<String> {
|
||||
self.scope().split_whitespace().map(str::to_string).collect()
|
||||
}
|
||||
|
||||
fn check_scope(&self, scope: Option<&String>) -> ApiResult<String> {
|
||||
let method_scope = self.scope();
|
||||
match scope {
|
||||
None => err!("Missing scope"),
|
||||
Some(scope) if scope == &method_scope => Ok(method_scope),
|
||||
Some(scope) => err!(format!("Scope ({scope}) not supported")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RefreshJwtClaims {
|
||||
// Not before
|
||||
pub nbf: i64,
|
||||
// Expiration time
|
||||
pub exp: i64,
|
||||
// Issuer
|
||||
pub iss: String,
|
||||
// Subject
|
||||
pub sub: AuthMethod,
|
||||
|
||||
pub device_token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AuthTokens {
|
||||
pub refresh_claims: RefreshJwtClaims,
|
||||
pub access_claims: LoginJwtClaims,
|
||||
}
|
||||
|
||||
impl AuthTokens {
|
||||
pub fn refresh_token(&self) -> String {
|
||||
encode_jwt(&self.refresh_claims)
|
||||
}
|
||||
|
||||
pub fn access_token(&self) -> String {
|
||||
self.access_claims.token()
|
||||
}
|
||||
|
||||
pub fn expires_in(&self) -> i64 {
|
||||
self.access_claims.expires_in()
|
||||
}
|
||||
|
||||
pub fn scope(&self) -> String {
|
||||
self.refresh_claims.sub.scope()
|
||||
}
|
||||
|
||||
// Create refresh_token and access_token with default validity
|
||||
pub fn new(device: &Device, user: &User, sub: AuthMethod) -> Self {
|
||||
let time_now = Utc::now();
|
||||
|
||||
let access_claims = LoginJwtClaims::default(device, user, &sub);
|
||||
|
||||
let refresh_claims = RefreshJwtClaims {
|
||||
nbf: time_now.timestamp(),
|
||||
exp: (time_now + *DEFAULT_REFRESH_VALIDITY).timestamp(),
|
||||
iss: JWT_LOGIN_ISSUER.to_string(),
|
||||
sub,
|
||||
device_token: device.refresh_token.clone(),
|
||||
};
|
||||
|
||||
Self {
|
||||
refresh_claims,
|
||||
access_claims,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn refresh_tokens(refresh_token: &str, conn: &mut DbConn) -> ApiResult<(Device, User, AuthTokens)> {
|
||||
let time_now = Utc::now();
|
||||
|
||||
let refresh_claims = match decode_refresh(refresh_token) {
|
||||
Err(err) => err!(format!("Impossible to read refresh_token: {err}")),
|
||||
Ok(claims) => claims,
|
||||
};
|
||||
|
||||
// Get device by refresh token
|
||||
let mut device = match Device::find_by_refresh_token(&refresh_claims.device_token, conn).await {
|
||||
None => err!("Invalid refresh token"),
|
||||
Some(device) => device,
|
||||
};
|
||||
|
||||
// Save to update `updated_at`.
|
||||
device.save(conn).await?;
|
||||
|
||||
let user = match User::find_by_uuid(&device.user_uuid, conn).await {
|
||||
None => err!("Impossible to find user"),
|
||||
Some(user) => user,
|
||||
};
|
||||
|
||||
if refresh_claims.exp < time_now.timestamp() {
|
||||
err!("Expired refresh token");
|
||||
}
|
||||
|
||||
let auth_tokens = match refresh_claims.sub {
|
||||
AuthMethod::Password => AuthTokens::new(&device, &user, refresh_claims.sub),
|
||||
_ => err!("Invalid auth method cannot refresh token"),
|
||||
};
|
||||
|
||||
Ok((device, user, auth_tokens))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use chrono::{NaiveDateTime, Utc};
|
||||
use data_encoding::{BASE64, BASE64URL};
|
||||
|
||||
use crate::{crypto, CONFIG};
|
||||
use crate::crypto;
|
||||
use core::fmt;
|
||||
|
||||
db_object! {
|
||||
|
@ -42,13 +43,12 @@ impl Device {
|
|||
|
||||
push_uuid: None,
|
||||
push_token: None,
|
||||
refresh_token: String::new(),
|
||||
refresh_token: crypto::encode_random_bytes::<64>(BASE64URL),
|
||||
twofactor_remember: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_twofactor_remember(&mut self) -> String {
|
||||
use data_encoding::BASE64;
|
||||
let twofactor_remember = crypto::encode_random_bytes::<180>(BASE64);
|
||||
self.twofactor_remember = Some(twofactor_remember.clone());
|
||||
|
||||
|
@ -59,61 +59,6 @@ impl Device {
|
|||
self.twofactor_remember = None;
|
||||
}
|
||||
|
||||
pub fn refresh_tokens(&mut self, user: &super::User, scope: Vec<String>) -> (String, i64) {
|
||||
// If there is no refresh token, we create one
|
||||
if self.refresh_token.is_empty() {
|
||||
use data_encoding::BASE64URL;
|
||||
self.refresh_token = crypto::encode_random_bytes::<64>(BASE64URL);
|
||||
}
|
||||
|
||||
// Update the expiration of the device and the last update date
|
||||
let time_now = Utc::now();
|
||||
self.updated_at = time_now.naive_utc();
|
||||
|
||||
// ---
|
||||
// Disabled these keys to be added to the JWT since they could cause the JWT to get too large
|
||||
// Also These key/value pairs are not used anywhere by either Vaultwarden or Bitwarden Clients
|
||||
// Because these might get used in the future, and they are added by the Bitwarden Server, lets keep it, but then commented out
|
||||
// ---
|
||||
// fn arg: orgs: Vec<super::UserOrganization>,
|
||||
// ---
|
||||
// let orgowner: Vec<_> = orgs.iter().filter(|o| o.atype == 0).map(|o| o.org_uuid.clone()).collect();
|
||||
// let orgadmin: Vec<_> = orgs.iter().filter(|o| o.atype == 1).map(|o| o.org_uuid.clone()).collect();
|
||||
// let orguser: Vec<_> = orgs.iter().filter(|o| o.atype == 2).map(|o| o.org_uuid.clone()).collect();
|
||||
// let orgmanager: Vec<_> = orgs.iter().filter(|o| o.atype == 3).map(|o| o.org_uuid.clone()).collect();
|
||||
|
||||
// Create the JWT claims struct, to send to the client
|
||||
use crate::auth::{encode_jwt, LoginJwtClaims, DEFAULT_VALIDITY, JWT_LOGIN_ISSUER};
|
||||
let claims = LoginJwtClaims {
|
||||
nbf: time_now.timestamp(),
|
||||
exp: (time_now + *DEFAULT_VALIDITY).timestamp(),
|
||||
iss: JWT_LOGIN_ISSUER.to_string(),
|
||||
sub: user.uuid.clone(),
|
||||
|
||||
premium: true,
|
||||
name: user.name.clone(),
|
||||
email: user.email.clone(),
|
||||
email_verified: !CONFIG.mail_enabled() || user.verified_at.is_some(),
|
||||
|
||||
// ---
|
||||
// Disabled these keys to be added to the JWT since they could cause the JWT to get too large
|
||||
// Also These key/value pairs are not used anywhere by either Vaultwarden or Bitwarden Clients
|
||||
// Because these might get used in the future, and they are added by the Bitwarden Server, lets keep it, but then commented out
|
||||
// See: https://github.com/dani-garcia/vaultwarden/issues/4156
|
||||
// ---
|
||||
// orgowner,
|
||||
// orgadmin,
|
||||
// orguser,
|
||||
// orgmanager,
|
||||
sstamp: user.security_stamp.clone(),
|
||||
device: self.uuid.clone(),
|
||||
scope,
|
||||
amr: vec!["Application".into()],
|
||||
};
|
||||
|
||||
(encode_jwt(&claims), DEFAULT_VALIDITY.num_seconds())
|
||||
}
|
||||
|
||||
pub fn is_push_device(&self) -> bool {
|
||||
matches!(DeviceType::from_i32(self.atype), DeviceType::Android | DeviceType::Ios)
|
||||
}
|
||||
|
|
Laden …
In neuem Issue referenzieren