mirror of
https://github.com/moghtech/komodo.git
synced 2026-04-29 12:43:26 -05:00
user link multiple primary login methods
This commit is contained in:
@@ -48,6 +48,91 @@ pub type SetLastSeenUpdateResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// Begin linking flow for a third party login. Response: [NoData].
|
||||
///
|
||||
/// First call this method when authenticated, then
|
||||
/// redirect user to /api/auth/{provider}/link.
|
||||
///
|
||||
/// 'provider' can be:
|
||||
/// - github
|
||||
/// - google
|
||||
/// - oidc
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoUserRequest)]
|
||||
#[response(BeginThirdPartyLoginLinkResponse)]
|
||||
#[error(serror::Error)]
|
||||
pub struct BeginThirdPartyLoginLink {}
|
||||
|
||||
#[typeshare]
|
||||
pub type BeginThirdPartyLoginLinkResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// Unlink a login. Response: [NoData].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoUserRequest)]
|
||||
#[response(UnlinkLoginResponse)]
|
||||
#[error(serror::Error)]
|
||||
pub struct UnlinkLogin {
|
||||
/// 'provider' can be:
|
||||
/// - Local
|
||||
/// - Github
|
||||
/// - Google
|
||||
/// - Oidc
|
||||
pub provider: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type UnlinkLoginResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// Update the calling users username.
|
||||
/// Response: [NoData].
|
||||
///
|
||||
/// Will fail if the new username is invalid or already taken.
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoUserRequest)]
|
||||
#[response(UpdateUsernameResponse)]
|
||||
#[error(serror::Error)]
|
||||
pub struct UpdateUsername {
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type UpdateUsernameResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// Update the calling user's password. Response: [NoData].
|
||||
///
|
||||
/// If the User was created using third party login method,
|
||||
/// using [UpdatePassword] adds or updates the Local linked (additional) login method.
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoUserRequest)]
|
||||
#[response(UpdatePasswordResponse)]
|
||||
#[error(serror::Error)]
|
||||
pub struct UpdatePassword {
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type UpdatePasswordResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// Create an api key for the calling user.
|
||||
/// Response: [CreateApiKeyResponse].
|
||||
///
|
||||
|
||||
@@ -3,48 +3,10 @@ use resolver_api::Resolve;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::{NoData, user::User};
|
||||
use crate::entities::user::User;
|
||||
|
||||
use super::KomodoWriteRequest;
|
||||
|
||||
//
|
||||
|
||||
/// **Only for local users**. Update the calling users username.
|
||||
/// Response: [NoData].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(UpdateUserUsernameResponse)]
|
||||
#[error(serror::Error)]
|
||||
pub struct UpdateUserUsername {
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type UpdateUserUsernameResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// **Only for local users**. Update the calling users password.
|
||||
/// Response: [NoData].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Resolve, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(KomodoWriteRequest)]
|
||||
#[response(UpdateUserPasswordResponse)]
|
||||
#[error(serror::Error)]
|
||||
pub struct UpdateUserPassword {
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type UpdateUserPasswordResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// **Admin only**. Delete a user.
|
||||
/// Admins can delete any non-admin user.
|
||||
/// Only Super Admin can delete an admin.
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use std::{collections::HashMap, sync::OnceLock};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use base64urlsafedata::Base64UrlSafeData;
|
||||
use derive_variants::{EnumVariants, ExtractVariant};
|
||||
use indexmap::IndexMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{AsRefStr, Display, EnumString};
|
||||
use typeshare::typeshare;
|
||||
use webauthn_rs::prelude::Passkey;
|
||||
|
||||
@@ -65,9 +68,14 @@ pub struct User {
|
||||
#[serde(default)]
|
||||
pub create_build_permissions: bool,
|
||||
|
||||
/// The user-type specific config.
|
||||
/// The primary user login.
|
||||
pub config: UserConfig,
|
||||
|
||||
/// Additional linked login methods.
|
||||
/// May not contain 'Service' type config.
|
||||
#[serde(default)]
|
||||
pub linked_logins: LinkedLoginsMap,
|
||||
|
||||
/// TOTP 2fa credentials
|
||||
#[serde(default)]
|
||||
pub totp: UserTotpConfig,
|
||||
@@ -94,7 +102,22 @@ pub struct User {
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, EnumVariants)]
|
||||
#[variant_derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Display,
|
||||
EnumString,
|
||||
AsRefStr
|
||||
)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
pub enum UserConfig {
|
||||
/// User that logs in with username / password
|
||||
@@ -121,6 +144,42 @@ impl Default for UserConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl UserConfig {
|
||||
pub fn sanitize(&mut self) {
|
||||
if let UserConfig::Local { password } = self {
|
||||
password.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct LinkedLoginsMap(HashMap<UserConfigVariant, UserConfig>);
|
||||
|
||||
impl LinkedLoginsMap {
|
||||
pub fn get(
|
||||
&self,
|
||||
variant: UserConfigVariant,
|
||||
) -> Option<&UserConfig> {
|
||||
self.0.get(&variant)
|
||||
}
|
||||
|
||||
pub fn update(&mut self, login: UserConfig) -> anyhow::Result<()> {
|
||||
if let UserConfig::Service { .. } = &login {
|
||||
return Err(anyhow!(
|
||||
"Cannot insert Service type configuration as additional login method."
|
||||
));
|
||||
}
|
||||
let key = login.extract_variant();
|
||||
self.0.insert(key, login);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, variant: UserConfigVariant) {
|
||||
self.0.remove(&variant);
|
||||
}
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct UserTotpConfig {
|
||||
@@ -160,9 +219,12 @@ impl UserPasskeyConfig {
|
||||
impl User {
|
||||
/// Prepares user object for transport by clearing any sensitive fields
|
||||
pub fn sanitize(&mut self) {
|
||||
if let UserConfig::Local { password } = &mut self.config {
|
||||
password.clear();
|
||||
}
|
||||
self.config.sanitize();
|
||||
self
|
||||
.linked_logins
|
||||
.0
|
||||
.values_mut()
|
||||
.for_each(UserConfig::sanitize);
|
||||
self.totp.sanitize();
|
||||
self.passkey.sanitize();
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ function fix_types() {
|
||||
.replaceAll("AlerterEndpointVariant", 'AlerterEndpoint["type"]')
|
||||
.replaceAll("AlertDataVariant", 'AlertData["type"]')
|
||||
.replaceAll("ServerTemplateConfigVariant", 'ServerTemplateConfig["type"]')
|
||||
.replaceAll("UserConfigVariant", 'UserConfig["type"]')
|
||||
// Add '| string' to env vars
|
||||
.replaceAll("EnvironmentVar[]", "EnvironmentVar[] | string")
|
||||
.replaceAll("IndexSet", "Array")
|
||||
|
||||
@@ -13,6 +13,8 @@ export type AuthResponses = {
|
||||
export type UserResponses = {
|
||||
PushRecentlyViewed: Types.PushRecentlyViewedResponse;
|
||||
SetLastSeenUpdate: Types.SetLastSeenUpdateResponse;
|
||||
UpdateUsername: Types.UpdateUsernameResponse;
|
||||
UpdatePassword: Types.UpdatePasswordResponse;
|
||||
CreateApiKey: Types.CreateApiKeyResponse;
|
||||
DeleteApiKey: Types.DeleteApiKeyResponse;
|
||||
BeginTotpEnrollment: Types.BeginTotpEnrollmentResponse;
|
||||
@@ -21,6 +23,8 @@ export type UserResponses = {
|
||||
BeginPasskeyEnrollment: Types.BeginPasskeyEnrollmentResponse;
|
||||
ConfirmPasskeyEnrollment: Types.ConfirmPasskeyEnrollmentResponse;
|
||||
UnenrollPasskey: Types.UnenrollPasskeyResponse;
|
||||
BeginThirdPartyLoginLink: Types.BeginThirdPartyLoginLinkResponse;
|
||||
UnlinkLogin: Types.UnlinkLoginResponse;
|
||||
};
|
||||
|
||||
export type ReadResponses = {
|
||||
@@ -319,8 +323,6 @@ export type WriteResponses = {
|
||||
|
||||
// ==== USER ====
|
||||
CreateLocalUser: Types.CreateLocalUserResponse;
|
||||
UpdateUserUsername: Types.UpdateUserUsernameResponse;
|
||||
UpdateUserPassword: Types.UpdateUserPasswordResponse;
|
||||
DeleteUser: Types.DeleteUserResponse;
|
||||
|
||||
// ==== SERVICE USER ====
|
||||
|
||||
@@ -350,6 +350,8 @@ export type _CreationChallengeResponse = any;
|
||||
/** Response for [BeginPasskeyEnrollment]. */
|
||||
export type BeginPasskeyEnrollmentResponse = _CreationChallengeResponse;
|
||||
|
||||
export type BeginThirdPartyLoginLinkResponse = NoData;
|
||||
|
||||
export enum Operation {
|
||||
None = "None",
|
||||
CreateSwarm = "CreateSwarm",
|
||||
@@ -1102,6 +1104,8 @@ export type UserConfig =
|
||||
description: string;
|
||||
}};
|
||||
|
||||
export type LinkedLoginsMap = Record<UserConfig["type"], UserConfig>;
|
||||
|
||||
export interface UserTotpConfig {
|
||||
/** TOTP shared secret, encrypted */
|
||||
secret: string;
|
||||
@@ -1138,8 +1142,13 @@ export interface User {
|
||||
create_server_permissions?: boolean;
|
||||
/** Whether the user has permission to create builds */
|
||||
create_build_permissions?: boolean;
|
||||
/** The user-type specific config. */
|
||||
/** The primary user login. */
|
||||
config: UserConfig;
|
||||
/**
|
||||
* Additional linked login methods.
|
||||
* May not contain 'Service' type config.
|
||||
*/
|
||||
linked_logins?: LinkedLoginsMap;
|
||||
/** TOTP 2fa credentials */
|
||||
totp?: UserTotpConfig;
|
||||
/** WebAuthn Passkey 2fa credentials */
|
||||
@@ -5481,12 +5490,16 @@ export type UnenrollPasskeyResponse = NoData;
|
||||
/** Response for [UnenrollTotp]. */
|
||||
export type UnenrollTotpResponse = NoData;
|
||||
|
||||
export type UnlinkLoginResponse = NoData;
|
||||
|
||||
export type UpdateDockerRegistryAccountResponse = DockerRegistryAccount;
|
||||
|
||||
export type UpdateGitProviderAccountResponse = GitProviderAccount;
|
||||
|
||||
export type UpdateOnboardingKeyResponse = OnboardingKey;
|
||||
|
||||
export type UpdatePasswordResponse = NoData;
|
||||
|
||||
export type UpdatePermissionOnResourceTypeResponse = NoData;
|
||||
|
||||
export type UpdatePermissionOnTargetResponse = NoData;
|
||||
@@ -5501,9 +5514,7 @@ export type UpdateUserAdminResponse = NoData;
|
||||
|
||||
export type UpdateUserBasePermissionsResponse = NoData;
|
||||
|
||||
export type UpdateUserPasswordResponse = NoData;
|
||||
|
||||
export type UpdateUserUsernameResponse = NoData;
|
||||
export type UpdateUsernameResponse = NoData;
|
||||
|
||||
export type UpdateVariableDescriptionResponse = Variable;
|
||||
|
||||
@@ -5862,6 +5873,20 @@ export interface BatchRunProcedure {
|
||||
export interface BeginPasskeyEnrollment {
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin linking flow for a third party login. Response: [NoData].
|
||||
*
|
||||
* First call this method when authenticated, then
|
||||
* redirect user to /api/auth/{provider}/link.
|
||||
*
|
||||
* 'provider' can be:
|
||||
* - github
|
||||
* - google
|
||||
* - oidc
|
||||
*/
|
||||
export interface BeginThirdPartyLoginLink {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts enrollment flow for TOTP 2FA auth support.
|
||||
* Response: [BeginTotpEnrollmentResponse]
|
||||
@@ -9813,6 +9838,18 @@ export interface UnenrollPasskey {
|
||||
export interface UnenrollTotp {
|
||||
}
|
||||
|
||||
/** Unlink a login. Response: [NoData]. */
|
||||
export interface UnlinkLogin {
|
||||
/**
|
||||
* 'provider' can be:
|
||||
* - Local
|
||||
* - Github
|
||||
* - Google
|
||||
* - Oidc
|
||||
*/
|
||||
provider: string;
|
||||
}
|
||||
|
||||
/** Unpauses all containers on the target server. Response: [Update] */
|
||||
export interface UnpauseAllContainers {
|
||||
/** Name or id */
|
||||
@@ -9989,6 +10026,16 @@ export interface UpdateOnboardingKey {
|
||||
create_builder?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the calling user's password. Response: [NoData].
|
||||
*
|
||||
* If the User was created using third party login method,
|
||||
* using [UpdatePassword] adds or updates the Local linked (additional) login method.
|
||||
*/
|
||||
export interface UpdatePassword {
|
||||
password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Admin only.** Update a user or user groups base permission level on a resource type.
|
||||
* Response: [NoData].
|
||||
@@ -10210,18 +10257,12 @@ export interface UpdateUserBasePermissions {
|
||||
}
|
||||
|
||||
/**
|
||||
* **Only for local users**. Update the calling users password.
|
||||
* Update the calling users username.
|
||||
* Response: [NoData].
|
||||
*
|
||||
* Will fail if the new username is invalid or already taken.
|
||||
*/
|
||||
export interface UpdateUserPassword {
|
||||
password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Only for local users**. Update the calling users username.
|
||||
* Response: [NoData].
|
||||
*/
|
||||
export interface UpdateUserUsername {
|
||||
export interface UpdateUsername {
|
||||
username: string;
|
||||
}
|
||||
|
||||
@@ -10691,6 +10732,8 @@ export type UserIdOrTwoFactor =
|
||||
export type UserRequest =
|
||||
| { type: "PushRecentlyViewed", params: PushRecentlyViewed }
|
||||
| { type: "SetLastSeenUpdate", params: SetLastSeenUpdate }
|
||||
| { type: "UpdateUsername", params: UpdateUsername }
|
||||
| { type: "UpdatePassword", params: UpdatePassword }
|
||||
| { type: "CreateApiKey", params: CreateApiKey }
|
||||
| { type: "DeleteApiKey", params: DeleteApiKey }
|
||||
| { type: "BeginTotpEnrollment", params: BeginTotpEnrollment }
|
||||
@@ -10698,7 +10741,9 @@ export type UserRequest =
|
||||
| { type: "UnenrollTotp", params: UnenrollTotp }
|
||||
| { type: "BeginPasskeyEnrollment", params: BeginPasskeyEnrollment }
|
||||
| { type: "ConfirmPasskeyEnrollment", params: ConfirmPasskeyEnrollment }
|
||||
| { type: "UnenrollPasskey", params: UnenrollPasskey };
|
||||
| { type: "UnenrollPasskey", params: UnenrollPasskey }
|
||||
| { type: "BeginThirdPartyLoginLink", params: BeginThirdPartyLoginLink }
|
||||
| { type: "UnlinkLogin", params: UnlinkLogin };
|
||||
|
||||
export type WriteRequest =
|
||||
| { type: "UpdateResourceMeta", params: UpdateResourceMeta }
|
||||
@@ -10777,8 +10822,6 @@ export type WriteRequest =
|
||||
| { type: "UpdateOnboardingKey", params: UpdateOnboardingKey }
|
||||
| { type: "DeleteOnboardingKey", params: DeleteOnboardingKey }
|
||||
| { type: "CreateLocalUser", params: CreateLocalUser }
|
||||
| { type: "UpdateUserUsername", params: UpdateUserUsername }
|
||||
| { type: "UpdateUserPassword", params: UpdateUserPassword }
|
||||
| { type: "DeleteUser", params: DeleteUser }
|
||||
| { type: "CreateServiceUser", params: CreateServiceUser }
|
||||
| { type: "UpdateServiceUserDescription", params: UpdateServiceUserDescription }
|
||||
|
||||
Reference in New Issue
Block a user