mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 01:27:31 +08:00
feat(users): implemented openidconnect (#5124)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
268
Cargo.lock
generated
268
Cargo.lock
generated
@ -1430,6 +1430,12 @@ dependencies = [
|
|||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base16ct"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base32"
|
name = "base32"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -2307,6 +2313,18 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-bigint"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"rand_core",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
@ -2328,6 +2346,34 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "curve25519-dalek"
|
||||||
|
version = "4.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"cpufeatures",
|
||||||
|
"curve25519-dalek-derive",
|
||||||
|
"digest",
|
||||||
|
"fiat-crypto",
|
||||||
|
"platforms",
|
||||||
|
"rustc_version 0.4.0",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "curve25519-dalek-derive"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.57",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.14.4"
|
version = "0.14.4"
|
||||||
@ -2677,6 +2723,44 @@ version = "1.0.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ecdsa"
|
||||||
|
version = "0.16.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
|
||||||
|
dependencies = [
|
||||||
|
"der",
|
||||||
|
"digest",
|
||||||
|
"elliptic-curve",
|
||||||
|
"rfc6979",
|
||||||
|
"signature",
|
||||||
|
"spki",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ed25519"
|
||||||
|
version = "2.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||||
|
dependencies = [
|
||||||
|
"pkcs8",
|
||||||
|
"signature",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ed25519-dalek"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
|
||||||
|
dependencies = [
|
||||||
|
"curve25519-dalek",
|
||||||
|
"ed25519",
|
||||||
|
"serde",
|
||||||
|
"sha2",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
@ -2686,6 +2770,27 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "elliptic-curve"
|
||||||
|
version = "0.13.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
|
||||||
|
dependencies = [
|
||||||
|
"base16ct",
|
||||||
|
"crypto-bigint",
|
||||||
|
"digest",
|
||||||
|
"ff",
|
||||||
|
"generic-array",
|
||||||
|
"group",
|
||||||
|
"hkdf",
|
||||||
|
"pem-rfc7468",
|
||||||
|
"pkcs8",
|
||||||
|
"rand_core",
|
||||||
|
"sec1",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.33"
|
version = "0.8.33"
|
||||||
@ -2915,6 +3020,22 @@ dependencies = [
|
|||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ff"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fiat-crypto"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "finl_unicode"
|
name = "finl_unicode"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@ -3197,6 +3318,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
"version_check",
|
"version_check",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3287,6 +3409,17 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "group"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
|
||||||
|
dependencies = [
|
||||||
|
"ff",
|
||||||
|
"rand_core",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.25"
|
version = "0.3.25"
|
||||||
@ -4550,6 +4683,26 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oauth2"
|
||||||
|
version = "4.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.13.1",
|
||||||
|
"chrono",
|
||||||
|
"getrandom",
|
||||||
|
"http 0.2.12",
|
||||||
|
"rand",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_path_to_error",
|
||||||
|
"sha2",
|
||||||
|
"thiserror",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.32.2"
|
version = "0.32.2"
|
||||||
@ -4596,6 +4749,38 @@ dependencies = [
|
|||||||
"utoipa",
|
"utoipa",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openidconnect"
|
||||||
|
version = "3.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f47e80a9cfae4462dd29c41e987edd228971d6565553fbc14b8a11e666d91590"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.13.1",
|
||||||
|
"chrono",
|
||||||
|
"dyn-clone",
|
||||||
|
"ed25519-dalek",
|
||||||
|
"hmac",
|
||||||
|
"http 0.2.12",
|
||||||
|
"itertools 0.10.5",
|
||||||
|
"log",
|
||||||
|
"oauth2",
|
||||||
|
"p256",
|
||||||
|
"p384",
|
||||||
|
"rand",
|
||||||
|
"rsa",
|
||||||
|
"serde",
|
||||||
|
"serde-value",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"serde_path_to_error",
|
||||||
|
"serde_plain",
|
||||||
|
"serde_with",
|
||||||
|
"sha2",
|
||||||
|
"subtle",
|
||||||
|
"thiserror",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opensearch"
|
name = "opensearch"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
@ -4743,6 +4928,15 @@ dependencies = [
|
|||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-float"
|
||||||
|
version = "2.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-multimap"
|
name = "ordered-multimap"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@ -4765,6 +4959,30 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "p256"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
|
||||||
|
dependencies = [
|
||||||
|
"ecdsa",
|
||||||
|
"elliptic-curve",
|
||||||
|
"primeorder",
|
||||||
|
"sha2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "p384"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209"
|
||||||
|
dependencies = [
|
||||||
|
"ecdsa",
|
||||||
|
"elliptic-curve",
|
||||||
|
"primeorder",
|
||||||
|
"sha2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@ -5040,6 +5258,12 @@ version = "0.3.30"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "platforms"
|
||||||
|
version = "3.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plotters"
|
name = "plotters"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
@ -5123,6 +5347,15 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "primeorder"
|
||||||
|
version = "0.13.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
|
||||||
|
dependencies = [
|
||||||
|
"elliptic-curve",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
@ -5584,6 +5817,16 @@ dependencies = [
|
|||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rfc6979"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
|
||||||
|
dependencies = [
|
||||||
|
"hmac",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.16.20"
|
version = "0.16.20"
|
||||||
@ -5728,6 +5971,7 @@ dependencies = [
|
|||||||
"num_cpus",
|
"num_cpus",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"openapi",
|
"openapi",
|
||||||
|
"openidconnect",
|
||||||
"openssl",
|
"openssl",
|
||||||
"pm_auth",
|
"pm_auth",
|
||||||
"qrcode",
|
"qrcode",
|
||||||
@ -6116,6 +6360,20 @@ version = "4.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sec1"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
|
||||||
|
dependencies = [
|
||||||
|
"base16ct",
|
||||||
|
"der",
|
||||||
|
"generic-array",
|
||||||
|
"pkcs8",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.9.2"
|
version = "2.9.2"
|
||||||
@ -6172,6 +6430,16 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde-value"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
|
||||||
|
dependencies = [
|
||||||
|
"ordered-float",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde-wasm-bindgen"
|
name = "serde-wasm-bindgen"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|||||||
@ -12,14 +12,14 @@ use crate::user::{
|
|||||||
},
|
},
|
||||||
AcceptInviteFromEmailRequest, AuthorizeResponse, BeginTotpResponse, ChangePasswordRequest,
|
AcceptInviteFromEmailRequest, AuthorizeResponse, BeginTotpResponse, ChangePasswordRequest,
|
||||||
ConnectAccountRequest, CreateInternalUserRequest, CreateUserAuthenticationMethodRequest,
|
ConnectAccountRequest, CreateInternalUserRequest, CreateUserAuthenticationMethodRequest,
|
||||||
DashboardEntryResponse, ForgotPasswordRequest, GetUserAuthenticationMethodsRequest,
|
DashboardEntryResponse, ForgotPasswordRequest, GetSsoAuthUrlRequest,
|
||||||
GetUserDetailsResponse, GetUserRoleDetailsRequest, GetUserRoleDetailsResponse,
|
GetUserAuthenticationMethodsRequest, GetUserDetailsResponse, GetUserRoleDetailsRequest,
|
||||||
InviteUserRequest, ListUsersResponse, ReInviteUserRequest, RecoveryCodes, ResetPasswordRequest,
|
GetUserRoleDetailsResponse, InviteUserRequest, ListUsersResponse, ReInviteUserRequest,
|
||||||
RotatePasswordRequest, SendVerifyEmailRequest, SignInResponse, SignUpRequest,
|
RecoveryCodes, ResetPasswordRequest, RotatePasswordRequest, SendVerifyEmailRequest,
|
||||||
SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, TokenOrPayloadResponse, TokenResponse,
|
SignInResponse, SignUpRequest, SignUpWithMerchantIdRequest, SsoSignInRequest,
|
||||||
TwoFactorAuthStatusResponse, UpdateUserAccountDetailsRequest,
|
SwitchMerchantIdRequest, TokenOrPayloadResponse, TokenResponse, TwoFactorAuthStatusResponse,
|
||||||
UpdateUserAuthenticationMethodRequest, UserFromEmailRequest, UserMerchantCreate,
|
UpdateUserAccountDetailsRequest, UpdateUserAuthenticationMethodRequest, UserFromEmailRequest,
|
||||||
VerifyEmailRequest, VerifyRecoveryCodeRequest, VerifyTotpRequest,
|
UserMerchantCreate, VerifyEmailRequest, VerifyRecoveryCodeRequest, VerifyTotpRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl ApiEventMetric for DashboardEntryResponse {
|
impl ApiEventMetric for DashboardEntryResponse {
|
||||||
@ -81,7 +81,9 @@ common_utils::impl_misc_api_event_type!(
|
|||||||
RecoveryCodes,
|
RecoveryCodes,
|
||||||
GetUserAuthenticationMethodsRequest,
|
GetUserAuthenticationMethodsRequest,
|
||||||
CreateUserAuthenticationMethodRequest,
|
CreateUserAuthenticationMethodRequest,
|
||||||
UpdateUserAuthenticationMethodRequest
|
UpdateUserAuthenticationMethodRequest,
|
||||||
|
GetSsoAuthUrlRequest,
|
||||||
|
SsoSignInRequest
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(feature = "dummy_connector")]
|
#[cfg(feature = "dummy_connector")]
|
||||||
|
|||||||
@ -306,8 +306,9 @@ pub struct OpenIdConnectPublicConfig {
|
|||||||
pub name: OpenIdProvider,
|
pub name: OpenIdProvider,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, strum::Display)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum OpenIdProvider {
|
pub enum OpenIdProvider {
|
||||||
Okta,
|
Okta,
|
||||||
}
|
}
|
||||||
@ -356,6 +357,17 @@ pub struct AuthMethodDetails {
|
|||||||
pub name: Option<OpenIdProvider>,
|
pub name: Option<OpenIdProvider>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct GetSsoAuthUrlRequest {
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct SsoSignInRequest {
|
||||||
|
pub state: Secret<String>,
|
||||||
|
pub code: Secret<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct AuthIdQueryParam {
|
pub struct AuthIdQueryParam {
|
||||||
pub auth_id: Option<String>,
|
pub auth_id: Option<String>,
|
||||||
|
|||||||
@ -12,6 +12,13 @@ impl UserAuthenticationMethodNew {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl UserAuthenticationMethod {
|
impl UserAuthenticationMethod {
|
||||||
|
pub async fn get_user_authentication_method_by_id(
|
||||||
|
conn: &PgPooledConn,
|
||||||
|
id: &str,
|
||||||
|
) -> StorageResult<Self> {
|
||||||
|
generics::generic_find_by_id::<<Self as HasTable>::Table, _, _>(conn, id.to_owned()).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn list_user_authentication_methods_for_auth_id(
|
pub async fn list_user_authentication_methods_for_auth_id(
|
||||||
conn: &PgPooledConn,
|
conn: &PgPooledConn,
|
||||||
auth_id: &str,
|
auth_id: &str,
|
||||||
|
|||||||
@ -66,6 +66,7 @@ mime = "0.3.17"
|
|||||||
nanoid = "0.4.0"
|
nanoid = "0.4.0"
|
||||||
num_cpus = "1.16.0"
|
num_cpus = "1.16.0"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
|
openidconnect = "3.5.0" # TODO: remove reqwest
|
||||||
openssl = "0.10.64"
|
openssl = "0.10.64"
|
||||||
qrcode = "0.14.0"
|
qrcode = "0.14.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|||||||
@ -130,7 +130,7 @@ pub mod routes {
|
|||||||
state,
|
state,
|
||||||
&req,
|
&req,
|
||||||
domain.into_inner(),
|
domain.into_inner(),
|
||||||
|_, _, domain: analytics::AnalyticsDomain, _| async {
|
|_, _: (), domain: analytics::AnalyticsDomain, _| async {
|
||||||
analytics::core::get_domain_info(domain)
|
analytics::core::get_domain_info(domain)
|
||||||
.await
|
.await
|
||||||
.map(ApplicationResponse::Json)
|
.map(ApplicationResponse::Json)
|
||||||
|
|||||||
@ -19,3 +19,6 @@ pub const REDIS_TOTP_PREFIX: &str = "TOTP_";
|
|||||||
pub const REDIS_RECOVERY_CODE_PREFIX: &str = "RC_";
|
pub const REDIS_RECOVERY_CODE_PREFIX: &str = "RC_";
|
||||||
pub const REDIS_TOTP_SECRET_PREFIX: &str = "TOTP_SEC_";
|
pub const REDIS_TOTP_SECRET_PREFIX: &str = "TOTP_SEC_";
|
||||||
pub const REDIS_TOTP_SECRET_TTL_IN_SECS: i64 = 15 * 60; // 15 minutes
|
pub const REDIS_TOTP_SECRET_TTL_IN_SECS: i64 = 15 * 60; // 15 minutes
|
||||||
|
|
||||||
|
pub const REDIS_SSO_PREFIX: &str = "SSO_";
|
||||||
|
pub const REDIS_SSO_TTL: i64 = 5 * 60; // 5 minutes
|
||||||
|
|||||||
@ -86,6 +86,8 @@ pub enum UserErrors {
|
|||||||
InvalidUserAuthMethodOperation,
|
InvalidUserAuthMethodOperation,
|
||||||
#[error("Auth config parsing error")]
|
#[error("Auth config parsing error")]
|
||||||
AuthConfigParsingError,
|
AuthConfigParsingError,
|
||||||
|
#[error("Invalid SSO request")]
|
||||||
|
SSOFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
|
impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
|
||||||
@ -219,6 +221,9 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
|
|||||||
Self::AuthConfigParsingError => {
|
Self::AuthConfigParsingError => {
|
||||||
AER::BadRequest(ApiError::new(sub_code, 45, self.get_error_message(), None))
|
AER::BadRequest(ApiError::new(sub_code, 45, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
|
Self::SSOFailed => {
|
||||||
|
AER::BadRequest(ApiError::new(sub_code, 46, self.get_error_message(), None))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -265,6 +270,7 @@ impl UserErrors {
|
|||||||
Self::UserAuthMethodAlreadyExists => "User auth method already exists",
|
Self::UserAuthMethodAlreadyExists => "User auth method already exists",
|
||||||
Self::InvalidUserAuthMethodOperation => "Invalid user auth method operation",
|
Self::InvalidUserAuthMethodOperation => "Invalid user auth method operation",
|
||||||
Self::AuthConfigParsingError => "Auth config parsing error",
|
Self::AuthConfigParsingError => "Auth config parsing error",
|
||||||
|
Self::SSOFailed => "Invalid SSO request",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use api_models::user::{self as user_api, InviteMultipleUserResponse};
|
use api_models::{
|
||||||
|
payments::RedirectionResponse,
|
||||||
|
user::{self as user_api, InviteMultipleUserResponse},
|
||||||
|
};
|
||||||
use common_utils::ext_traits::ValueExt;
|
use common_utils::ext_traits::ValueExt;
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
use diesel_models::user_role::UserRoleUpdate;
|
use diesel_models::user_role::UserRoleUpdate;
|
||||||
@ -13,7 +16,7 @@ use diesel_models::{
|
|||||||
use error_stack::{report, ResultExt};
|
use error_stack::{report, ResultExt};
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
use external_services::email::EmailData;
|
use external_services::email::EmailData;
|
||||||
use masking::{ExposeInterface, PeekInterface};
|
use masking::{ExposeInterface, PeekInterface, Secret};
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
use router_env::env;
|
use router_env::env;
|
||||||
use router_env::logger;
|
use router_env::logger;
|
||||||
@ -26,7 +29,7 @@ use crate::services::email::types as email_types;
|
|||||||
use crate::{
|
use crate::{
|
||||||
consts,
|
consts,
|
||||||
routes::{app::ReqState, SessionState},
|
routes::{app::ReqState, SessionState},
|
||||||
services::{authentication as auth, authorization::roles, ApplicationResponse},
|
services::{authentication as auth, authorization::roles, openidconnect, ApplicationResponse},
|
||||||
types::{domain, transformers::ForeignInto},
|
types::{domain, transformers::ForeignInto},
|
||||||
utils::{self, user::two_factor_auth as tfa_utils},
|
utils::{self, user::two_factor_auth as tfa_utils},
|
||||||
};
|
};
|
||||||
@ -2198,3 +2201,114 @@ pub async fn list_user_authentication_methods(
|
|||||||
.collect::<UserResult<_>>()?,
|
.collect::<UserResult<_>>()?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_sso_auth_url(
|
||||||
|
state: SessionState,
|
||||||
|
request: user_api::GetSsoAuthUrlRequest,
|
||||||
|
) -> UserResponse<()> {
|
||||||
|
let user_authentication_method = state
|
||||||
|
.store
|
||||||
|
.get_user_authentication_method_by_id(request.id.as_str())
|
||||||
|
.await
|
||||||
|
.to_not_found_response(UserErrors::InvalidUserAuthMethodOperation)?;
|
||||||
|
|
||||||
|
let open_id_private_config =
|
||||||
|
utils::user::decrypt_oidc_private_config(&state, user_authentication_method.private_config)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let open_id_public_config: user_api::OpenIdConnectPublicConfig = user_authentication_method
|
||||||
|
.public_config
|
||||||
|
.ok_or(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Public config not present")?
|
||||||
|
.parse_value("OpenIdConnectPublicConfig")
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("unable to parse OpenIdConnectPublicConfig")?;
|
||||||
|
|
||||||
|
let oidc_state = Secret::new(nanoid::nanoid!());
|
||||||
|
utils::user::set_sso_id_in_redis(&state, oidc_state.clone(), request.id).await?;
|
||||||
|
|
||||||
|
let redirect_url =
|
||||||
|
utils::user::get_oidc_sso_redirect_url(&state, &open_id_public_config.name.to_string());
|
||||||
|
|
||||||
|
openidconnect::get_authorization_url(
|
||||||
|
state,
|
||||||
|
redirect_url,
|
||||||
|
oidc_state,
|
||||||
|
open_id_private_config.base_url.into(),
|
||||||
|
open_id_private_config.client_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|url| {
|
||||||
|
ApplicationResponse::JsonForRedirection(RedirectionResponse {
|
||||||
|
headers: Vec::with_capacity(0),
|
||||||
|
return_url: String::new(),
|
||||||
|
http_method: String::new(),
|
||||||
|
params: Vec::with_capacity(0),
|
||||||
|
return_url_with_query_params: url.to_string(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sso_sign(
|
||||||
|
state: SessionState,
|
||||||
|
request: user_api::SsoSignInRequest,
|
||||||
|
user_from_single_purpose_token: Option<auth::UserFromSinglePurposeToken>,
|
||||||
|
) -> UserResponse<user_api::TokenResponse> {
|
||||||
|
let authentication_method_id =
|
||||||
|
utils::user::get_sso_id_from_redis(&state, request.state.clone()).await?;
|
||||||
|
|
||||||
|
let user_authentication_method = state
|
||||||
|
.store
|
||||||
|
.get_user_authentication_method_by_id(&authentication_method_id)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
|
|
||||||
|
let open_id_private_config =
|
||||||
|
utils::user::decrypt_oidc_private_config(&state, user_authentication_method.private_config)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let open_id_public_config: user_api::OpenIdConnectPublicConfig = user_authentication_method
|
||||||
|
.public_config
|
||||||
|
.ok_or(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Public config not present")?
|
||||||
|
.parse_value("OpenIdConnectPublicConfig")
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("unable to parse OpenIdConnectPublicConfig")?;
|
||||||
|
|
||||||
|
let redirect_url =
|
||||||
|
utils::user::get_oidc_sso_redirect_url(&state, &open_id_public_config.name.to_string());
|
||||||
|
let email = openidconnect::get_user_email_from_oidc_provider(
|
||||||
|
&state,
|
||||||
|
redirect_url,
|
||||||
|
request.state,
|
||||||
|
open_id_private_config.base_url.into(),
|
||||||
|
open_id_private_config.client_id,
|
||||||
|
request.code,
|
||||||
|
open_id_private_config.client_secret,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// TODO: Use config to handle not found error
|
||||||
|
let user_from_db = state
|
||||||
|
.global_store
|
||||||
|
.find_user_by_email(&email.into_inner())
|
||||||
|
.await
|
||||||
|
.map(Into::into)
|
||||||
|
.to_not_found_response(UserErrors::UserNotFound)?;
|
||||||
|
|
||||||
|
let next_flow = if let Some(user_from_single_purpose_token) = user_from_single_purpose_token {
|
||||||
|
let current_flow =
|
||||||
|
domain::CurrentFlow::new(user_from_single_purpose_token, domain::SPTFlow::SSO.into())?;
|
||||||
|
current_flow.next(user_from_db, &state).await?
|
||||||
|
} else {
|
||||||
|
domain::NextFlow::from_origin(domain::Origin::SignInWithSSO, user_from_db, &state).await?
|
||||||
|
};
|
||||||
|
|
||||||
|
let token = next_flow.get_token(&state).await?;
|
||||||
|
let response = user_api::TokenResponse {
|
||||||
|
token: token.clone(),
|
||||||
|
token_type: next_flow.get_flow().into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
auth::cookies::set_cookie_response(response, token)
|
||||||
|
}
|
||||||
|
|||||||
@ -2971,6 +2971,15 @@ impl UserAuthenticationMethodInterface for KafkaStore {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_user_authentication_method_by_id(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
|
||||||
|
self.diesel_store
|
||||||
|
.get_user_authentication_method_by_id(id)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
async fn list_user_authentication_methods_for_auth_id(
|
async fn list_user_authentication_methods_for_auth_id(
|
||||||
&self,
|
&self,
|
||||||
auth_id: &str,
|
auth_id: &str,
|
||||||
|
|||||||
@ -16,6 +16,11 @@ pub trait UserAuthenticationMethodInterface {
|
|||||||
user_authentication_method: storage::UserAuthenticationMethodNew,
|
user_authentication_method: storage::UserAuthenticationMethodNew,
|
||||||
) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError>;
|
) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError>;
|
||||||
|
|
||||||
|
async fn get_user_authentication_method_by_id(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError>;
|
||||||
|
|
||||||
async fn list_user_authentication_methods_for_auth_id(
|
async fn list_user_authentication_methods_for_auth_id(
|
||||||
&self,
|
&self,
|
||||||
auth_id: &str,
|
auth_id: &str,
|
||||||
@ -47,6 +52,17 @@ impl UserAuthenticationMethodInterface for Store {
|
|||||||
.map_err(|error| report!(errors::StorageError::from(error)))
|
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
async fn get_user_authentication_method_by_id(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
|
||||||
|
let conn = connection::pg_connection_write(self).await?;
|
||||||
|
storage::UserAuthenticationMethod::get_user_authentication_method_by_id(&conn, id)
|
||||||
|
.await
|
||||||
|
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn list_user_authentication_methods_for_auth_id(
|
async fn list_user_authentication_methods_for_auth_id(
|
||||||
&self,
|
&self,
|
||||||
@ -120,6 +136,28 @@ impl UserAuthenticationMethodInterface for MockDb {
|
|||||||
Ok(user_authentication_method)
|
Ok(user_authentication_method)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
async fn get_user_authentication_method_by_id(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
|
||||||
|
let user_authentication_methods = self.user_authentication_methods.lock().await;
|
||||||
|
|
||||||
|
let user_authentication_method = user_authentication_methods
|
||||||
|
.iter()
|
||||||
|
.find(|&auth_method_inner| auth_method_inner.id == id);
|
||||||
|
|
||||||
|
if let Some(user_authentication_method) = user_authentication_method {
|
||||||
|
Ok(user_authentication_method.to_owned())
|
||||||
|
} else {
|
||||||
|
return Err(errors::StorageError::ValueNotFound(format!(
|
||||||
|
"No user authentication method found for id = {}",
|
||||||
|
id
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn list_user_authentication_methods_for_auth_id(
|
async fn list_user_authentication_methods_for_auth_id(
|
||||||
&self,
|
&self,
|
||||||
auth_id: &str,
|
auth_id: &str,
|
||||||
|
|||||||
@ -1351,6 +1351,8 @@ impl User {
|
|||||||
route = route
|
route = route
|
||||||
.service(web::resource("").route(web::get().to(get_user_details)))
|
.service(web::resource("").route(web::get().to(get_user_details)))
|
||||||
.service(web::resource("/v2/signin").route(web::post().to(user_signin)))
|
.service(web::resource("/v2/signin").route(web::post().to(user_signin)))
|
||||||
|
// signin/signup with sso using openidconnect
|
||||||
|
.service(web::resource("/oidc").route(web::post().to(sso_sign)))
|
||||||
.service(web::resource("/signout").route(web::post().to(signout)))
|
.service(web::resource("/signout").route(web::post().to(signout)))
|
||||||
.service(web::resource("/rotate_password").route(web::post().to(rotate_password)))
|
.service(web::resource("/rotate_password").route(web::post().to(rotate_password)))
|
||||||
.service(web::resource("/change_password").route(web::post().to(change_password)))
|
.service(web::resource("/change_password").route(web::post().to(change_password)))
|
||||||
@ -1414,7 +1416,8 @@ impl User {
|
|||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/list").route(web::get().to(list_user_authentication_methods)),
|
web::resource("/list").route(web::get().to(list_user_authentication_methods)),
|
||||||
),
|
)
|
||||||
|
.service(web::resource("/url").route(web::get().to(get_sso_auth_url))),
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
|
|||||||
@ -27,7 +27,7 @@ pub async fn dummy_connector_authorize_payment(
|
|||||||
state,
|
state,
|
||||||
&req,
|
&req,
|
||||||
payload,
|
payload,
|
||||||
|state, _, req, _| core::payment_authorize(state, req),
|
|state, _: (), req, _| core::payment_authorize(state, req),
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
)
|
)
|
||||||
@ -51,7 +51,7 @@ pub async fn dummy_connector_complete_payment(
|
|||||||
state,
|
state,
|
||||||
&req,
|
&req,
|
||||||
payload,
|
payload,
|
||||||
|state, _, req, _| core::payment_complete(state, req),
|
|state, _: (), req, _| core::payment_complete(state, req),
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
)
|
)
|
||||||
@ -70,7 +70,7 @@ pub async fn dummy_connector_payment(
|
|||||||
state,
|
state,
|
||||||
&req,
|
&req,
|
||||||
payload,
|
payload,
|
||||||
|state, _, req, _| core::payment(state, req),
|
|state, _: (), req, _| core::payment(state, req),
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
)
|
)
|
||||||
@ -90,7 +90,7 @@ pub async fn dummy_connector_payment_data(
|
|||||||
state,
|
state,
|
||||||
&req,
|
&req,
|
||||||
payload,
|
payload,
|
||||||
|state, _, req, _| core::payment_data(state, req),
|
|state, _: (), req, _| core::payment_data(state, req),
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
)
|
)
|
||||||
@ -111,7 +111,7 @@ pub async fn dummy_connector_refund(
|
|||||||
state,
|
state,
|
||||||
&req,
|
&req,
|
||||||
payload,
|
payload,
|
||||||
|state, _, req, _| core::refund_payment(state, req),
|
|state, _: (), req, _| core::refund_payment(state, req),
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
)
|
)
|
||||||
@ -131,7 +131,7 @@ pub async fn dummy_connector_refund_data(
|
|||||||
state,
|
state,
|
||||||
&req,
|
&req,
|
||||||
payload,
|
payload,
|
||||||
|state, _, req, _| core::refund_data(state, req),
|
|state, _: (), req, _| core::refund_data(state, req),
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -34,7 +34,7 @@ pub async fn deep_health_check(
|
|||||||
state,
|
state,
|
||||||
&request,
|
&request,
|
||||||
(),
|
(),
|
||||||
|state, _, _, _| deep_health_check_func(state),
|
|state, _: (), _, _| deep_health_check_func(state),
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
))
|
))
|
||||||
|
|||||||
@ -230,7 +230,9 @@ impl From<Flow> for ApiIdentifier {
|
|||||||
| Flow::TwoFactorAuthStatus
|
| Flow::TwoFactorAuthStatus
|
||||||
| Flow::CreateUserAuthenticationMethod
|
| Flow::CreateUserAuthenticationMethod
|
||||||
| Flow::UpdateUserAuthenticationMethod
|
| Flow::UpdateUserAuthenticationMethod
|
||||||
| Flow::ListUserAuthenticationMethods => Self::User,
|
| Flow::ListUserAuthenticationMethods
|
||||||
|
| Flow::GetSsoAuthUrl
|
||||||
|
| Flow::SignInWithSso => Self::User,
|
||||||
|
|
||||||
Flow::ListRoles
|
Flow::ListRoles
|
||||||
| Flow::GetRole
|
| Flow::GetRole
|
||||||
|
|||||||
@ -72,7 +72,7 @@ pub async fn user_signup(
|
|||||||
state,
|
state,
|
||||||
&http_req,
|
&http_req,
|
||||||
req_payload.clone(),
|
req_payload.clone(),
|
||||||
|state, _, req_body, _| async move {
|
|state, _: (), req_body, _| async move {
|
||||||
if let Some(true) = is_token_only {
|
if let Some(true) = is_token_only {
|
||||||
user_core::signup_token_only_flow(state, req_body).await
|
user_core::signup_token_only_flow(state, req_body).await
|
||||||
} else {
|
} else {
|
||||||
@ -99,7 +99,7 @@ pub async fn user_signin(
|
|||||||
state,
|
state,
|
||||||
&http_req,
|
&http_req,
|
||||||
req_payload.clone(),
|
req_payload.clone(),
|
||||||
|state, _, req_body, _| async move {
|
|state, _: (), req_body, _| async move {
|
||||||
if let Some(true) = is_token_only {
|
if let Some(true) = is_token_only {
|
||||||
user_core::signin_token_only_flow(state, req_body).await
|
user_core::signin_token_only_flow(state, req_body).await
|
||||||
} else {
|
} else {
|
||||||
@ -127,7 +127,7 @@ pub async fn user_connect_account(
|
|||||||
state,
|
state,
|
||||||
&http_req,
|
&http_req,
|
||||||
req_payload.clone(),
|
req_payload.clone(),
|
||||||
|state, _, req_body, _| user_core::connect_account(state, req_body, auth_id.clone()),
|
|state, _: (), req_body, _| user_core::connect_account(state, req_body, auth_id.clone()),
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
))
|
))
|
||||||
@ -397,7 +397,7 @@ pub async fn forgot_password(
|
|||||||
state.clone(),
|
state.clone(),
|
||||||
&req,
|
&req,
|
||||||
payload.into_inner(),
|
payload.into_inner(),
|
||||||
|state, _, payload, _| user_core::forgot_password(state, payload, auth_id.clone()),
|
|state, _: (), payload, _| user_core::forgot_password(state, payload, auth_id.clone()),
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
))
|
))
|
||||||
@ -432,7 +432,7 @@ pub async fn reset_password(
|
|||||||
state.clone(),
|
state.clone(),
|
||||||
&req,
|
&req,
|
||||||
payload.into_inner(),
|
payload.into_inner(),
|
||||||
|state, _, payload, _| user_core::reset_password(state, payload),
|
|state, _: (), payload, _| user_core::reset_password(state, payload),
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
))
|
))
|
||||||
@ -522,7 +522,7 @@ pub async fn accept_invite_from_email(
|
|||||||
state.clone(),
|
state.clone(),
|
||||||
&req,
|
&req,
|
||||||
payload.into_inner(),
|
payload.into_inner(),
|
||||||
|state, _, request_payload, _| {
|
|state, _: (), request_payload, _| {
|
||||||
user_core::accept_invite_from_email(state, request_payload)
|
user_core::accept_invite_from_email(state, request_payload)
|
||||||
},
|
},
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
@ -560,7 +560,7 @@ pub async fn verify_email(
|
|||||||
state,
|
state,
|
||||||
&http_req,
|
&http_req,
|
||||||
json_payload.into_inner(),
|
json_payload.into_inner(),
|
||||||
|state, _, req_payload, _| user_core::verify_email(state, req_payload),
|
|state, _: (), req_payload, _| user_core::verify_email(state, req_payload),
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
))
|
))
|
||||||
@ -582,7 +582,9 @@ pub async fn verify_email_request(
|
|||||||
state.clone(),
|
state.clone(),
|
||||||
&http_req,
|
&http_req,
|
||||||
json_payload.into_inner(),
|
json_payload.into_inner(),
|
||||||
|state, _, req_body, _| user_core::send_verification_mail(state, req_body, auth_id.clone()),
|
|state, _: (), req_body, _| {
|
||||||
|
user_core::send_verification_mail(state, req_body, auth_id.clone())
|
||||||
|
},
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
))
|
))
|
||||||
@ -774,6 +776,50 @@ pub async fn check_two_factor_auth_status(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_sso_auth_url(
|
||||||
|
state: web::Data<AppState>,
|
||||||
|
req: HttpRequest,
|
||||||
|
query: web::Query<user_api::GetSsoAuthUrlRequest>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
let flow = Flow::GetSsoAuthUrl;
|
||||||
|
let payload = query.into_inner();
|
||||||
|
Box::pin(api::server_wrap(
|
||||||
|
flow,
|
||||||
|
state.clone(),
|
||||||
|
&req,
|
||||||
|
payload,
|
||||||
|
|state, _: (), req, _| user_core::get_sso_auth_url(state, req),
|
||||||
|
&auth::NoAuth,
|
||||||
|
api_locking::LockAction::NotApplicable,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sso_sign(
|
||||||
|
state: web::Data<AppState>,
|
||||||
|
req: HttpRequest,
|
||||||
|
json_payload: web::Json<user_api::SsoSignInRequest>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
let flow = Flow::SignInWithSso;
|
||||||
|
let payload = json_payload.into_inner();
|
||||||
|
Box::pin(api::server_wrap(
|
||||||
|
flow,
|
||||||
|
state.clone(),
|
||||||
|
&req,
|
||||||
|
payload,
|
||||||
|
|state, user: Option<auth::UserFromSinglePurposeToken>, payload, _| {
|
||||||
|
user_core::sso_sign(state, payload, user)
|
||||||
|
},
|
||||||
|
auth::auth_type(
|
||||||
|
&auth::NoAuth,
|
||||||
|
&auth::SinglePurposeJWTAuth(TokenPurpose::SSO),
|
||||||
|
req.headers(),
|
||||||
|
),
|
||||||
|
api_locking::LockAction::NotApplicable,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn create_user_authentication_method(
|
pub async fn create_user_authentication_method(
|
||||||
state: web::Data<AppState>,
|
state: web::Data<AppState>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
@ -824,7 +870,7 @@ pub async fn list_user_authentication_methods(
|
|||||||
state.clone(),
|
state.clone(),
|
||||||
&req,
|
&req,
|
||||||
query.into_inner(),
|
query.into_inner(),
|
||||||
|state, _, req, _| user_core::list_user_authentication_methods(state, req),
|
|state, _: (), req, _| user_core::list_user_authentication_methods(state, req),
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
))
|
))
|
||||||
|
|||||||
@ -14,6 +14,9 @@ pub mod pm_auth;
|
|||||||
#[cfg(feature = "recon")]
|
#[cfg(feature = "recon")]
|
||||||
pub mod recon;
|
pub mod recon;
|
||||||
|
|
||||||
|
#[cfg(feature = "olap")]
|
||||||
|
pub mod openidconnect;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
|
|||||||
@ -80,7 +80,7 @@ fn get_base_client(
|
|||||||
|
|
||||||
// We may need to use outbound proxy to connect to external world.
|
// We may need to use outbound proxy to connect to external world.
|
||||||
// Precedence will be the environment variables, followed by the config.
|
// Precedence will be the environment variables, followed by the config.
|
||||||
pub(super) fn create_client(
|
pub fn create_client(
|
||||||
proxy_config: &Proxy,
|
proxy_config: &Proxy,
|
||||||
should_bypass_proxy: bool,
|
should_bypass_proxy: bool,
|
||||||
client_certificate: Option<masking::Secret<String>>,
|
client_certificate: Option<masking::Secret<String>>,
|
||||||
|
|||||||
@ -261,6 +261,20 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<A, T> AuthenticateAndFetch<Option<T>, A> for NoAuth
|
||||||
|
where
|
||||||
|
A: SessionStateInfo + Sync,
|
||||||
|
{
|
||||||
|
async fn authenticate_and_fetch(
|
||||||
|
&self,
|
||||||
|
_request_headers: &HeaderMap,
|
||||||
|
_state: &A,
|
||||||
|
) -> RouterResult<(Option<T>, AuthenticationType)> {
|
||||||
|
Ok((None, AuthenticationType::NoAuth))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<A> AuthenticateAndFetch<AuthenticationData, A> for ApiKeyAuth
|
impl<A> AuthenticateAndFetch<AuthenticationData, A> for ApiKeyAuth
|
||||||
where
|
where
|
||||||
@ -372,6 +386,40 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "olap")]
|
||||||
|
#[async_trait]
|
||||||
|
impl<A> AuthenticateAndFetch<Option<UserFromSinglePurposeToken>, A> for SinglePurposeJWTAuth
|
||||||
|
where
|
||||||
|
A: SessionStateInfo + Sync,
|
||||||
|
{
|
||||||
|
async fn authenticate_and_fetch(
|
||||||
|
&self,
|
||||||
|
request_headers: &HeaderMap,
|
||||||
|
state: &A,
|
||||||
|
) -> RouterResult<(Option<UserFromSinglePurposeToken>, AuthenticationType)> {
|
||||||
|
let payload = parse_jwt_payload::<A, SinglePurposeToken>(request_headers, state).await?;
|
||||||
|
if payload.check_in_blacklist(state).await? {
|
||||||
|
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.0 != payload.purpose {
|
||||||
|
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
Some(UserFromSinglePurposeToken {
|
||||||
|
user_id: payload.user_id.clone(),
|
||||||
|
origin: payload.origin.clone(),
|
||||||
|
path: payload.path,
|
||||||
|
}),
|
||||||
|
AuthenticationType::SinglePurposeJwt {
|
||||||
|
user_id: payload.user_id,
|
||||||
|
purpose: payload.purpose,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "olap")]
|
#[cfg(feature = "olap")]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SinglePurposeOrLoginTokenAuth(pub TokenPurpose);
|
pub struct SinglePurposeOrLoginTokenAuth(pub TokenPurpose);
|
||||||
|
|||||||
197
crates/router/src/services/openidconnect.rs
Normal file
197
crates/router/src/services/openidconnect.rs
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
use error_stack::ResultExt;
|
||||||
|
use masking::{ExposeInterface, Secret};
|
||||||
|
use oidc::TokenResponse;
|
||||||
|
use openidconnect::{self as oidc, core as oidc_core};
|
||||||
|
use redis_interface::RedisConnectionPool;
|
||||||
|
use storage_impl::errors::ApiClientError;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
consts,
|
||||||
|
core::errors::{UserErrors, UserResult},
|
||||||
|
routes::SessionState,
|
||||||
|
services::api::client,
|
||||||
|
types::domain::user::UserEmail,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn get_authorization_url(
|
||||||
|
state: SessionState,
|
||||||
|
redirect_url: String,
|
||||||
|
redirect_state: Secret<String>,
|
||||||
|
base_url: Secret<String>,
|
||||||
|
client_id: Secret<String>,
|
||||||
|
) -> UserResult<url::Url> {
|
||||||
|
let discovery_document = get_discovery_document(base_url, &state).await?;
|
||||||
|
|
||||||
|
let (auth_url, csrf_token, nonce) =
|
||||||
|
get_oidc_core_client(discovery_document, client_id, None, redirect_url)?
|
||||||
|
.authorize_url(
|
||||||
|
oidc_core::CoreAuthenticationFlow::AuthorizationCode,
|
||||||
|
|| oidc::CsrfToken::new(redirect_state.expose()),
|
||||||
|
oidc::Nonce::new_random,
|
||||||
|
)
|
||||||
|
.add_scope(oidc::Scope::new("email".to_string()))
|
||||||
|
.url();
|
||||||
|
|
||||||
|
// Save csrf & nonce as key value respectively
|
||||||
|
let key = get_oidc_redis_key(csrf_token.secret());
|
||||||
|
get_redis_connection(&state)?
|
||||||
|
.set_key_with_expiry(&key, nonce.secret(), consts::user::REDIS_SSO_TTL)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Failed to save csrf-nonce in redis")?;
|
||||||
|
|
||||||
|
Ok(auth_url)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_email_from_oidc_provider(
|
||||||
|
state: &SessionState,
|
||||||
|
redirect_url: String,
|
||||||
|
redirect_state: Secret<String>,
|
||||||
|
base_url: Secret<String>,
|
||||||
|
client_id: Secret<String>,
|
||||||
|
authorization_code: Secret<String>,
|
||||||
|
client_secret: Secret<String>,
|
||||||
|
) -> UserResult<UserEmail> {
|
||||||
|
let nonce = get_nonce_from_redis(state, &redirect_state).await?;
|
||||||
|
let discovery_document = get_discovery_document(base_url, state).await?;
|
||||||
|
let client = get_oidc_core_client(
|
||||||
|
discovery_document,
|
||||||
|
client_id,
|
||||||
|
Some(client_secret),
|
||||||
|
redirect_url,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let nonce_clone = nonce.clone();
|
||||||
|
client
|
||||||
|
.authorize_url(
|
||||||
|
oidc_core::CoreAuthenticationFlow::AuthorizationCode,
|
||||||
|
|| oidc::CsrfToken::new(redirect_state.expose()),
|
||||||
|
|| nonce_clone,
|
||||||
|
)
|
||||||
|
.add_scope(oidc::Scope::new("email".to_string()));
|
||||||
|
|
||||||
|
// Send request to OpenId provider with authorization code
|
||||||
|
let token_response = client
|
||||||
|
.exchange_code(oidc::AuthorizationCode::new(authorization_code.expose()))
|
||||||
|
.request_async(|req| get_oidc_reqwest_client(state, req))
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Failed to exchange code and fetch oidc token")?;
|
||||||
|
|
||||||
|
// Fetch id token from response
|
||||||
|
let id_token = token_response
|
||||||
|
.id_token()
|
||||||
|
.ok_or(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Id Token not provided in token response")?;
|
||||||
|
|
||||||
|
// Verify id token
|
||||||
|
let id_token_claims = id_token
|
||||||
|
.claims(&client.id_token_verifier(), &nonce)
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Failed to verify id token")?;
|
||||||
|
|
||||||
|
// Get email from token
|
||||||
|
let email_from_token = id_token_claims
|
||||||
|
.email()
|
||||||
|
.map(|email| email.to_string())
|
||||||
|
.ok_or(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("OpenID Provider Didnt provide email")?;
|
||||||
|
|
||||||
|
UserEmail::new(Secret::new(email_from_token))
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Failed to create email type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Cache Discovery Document
|
||||||
|
async fn get_discovery_document(
|
||||||
|
base_url: Secret<String>,
|
||||||
|
state: &SessionState,
|
||||||
|
) -> UserResult<oidc_core::CoreProviderMetadata> {
|
||||||
|
let issuer_url =
|
||||||
|
oidc::IssuerUrl::new(base_url.expose()).change_context(UserErrors::InternalServerError)?;
|
||||||
|
oidc_core::CoreProviderMetadata::discover_async(issuer_url, |req| {
|
||||||
|
get_oidc_reqwest_client(state, req)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_oidc_core_client(
|
||||||
|
discovery_document: oidc_core::CoreProviderMetadata,
|
||||||
|
client_id: Secret<String>,
|
||||||
|
client_secret: Option<Secret<String>>,
|
||||||
|
redirect_url: String,
|
||||||
|
) -> UserResult<oidc_core::CoreClient> {
|
||||||
|
let client_id = oidc::ClientId::new(client_id.expose());
|
||||||
|
let client_secret = client_secret.map(|secret| oidc::ClientSecret::new(secret.expose()));
|
||||||
|
let redirect_url = oidc::RedirectUrl::new(redirect_url)
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Error creating redirect URL type")?;
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
oidc_core::CoreClient::from_provider_metadata(discovery_document, client_id, client_secret)
|
||||||
|
.set_redirect_uri(redirect_url),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_nonce_from_redis(
|
||||||
|
state: &SessionState,
|
||||||
|
redirect_state: &Secret<String>,
|
||||||
|
) -> UserResult<oidc::Nonce> {
|
||||||
|
let redis_connection = get_redis_connection(state)?;
|
||||||
|
let redirect_state = redirect_state.clone().expose();
|
||||||
|
let key = get_oidc_redis_key(&redirect_state);
|
||||||
|
redis_connection
|
||||||
|
.get_key::<Option<String>>(&key)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Error Fetching CSRF from redis")?
|
||||||
|
.map(oidc::Nonce::new)
|
||||||
|
.ok_or(UserErrors::SSOFailed)
|
||||||
|
.attach_printable("Cannot find csrf in redis. Csrf invalid or expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_oidc_reqwest_client(
|
||||||
|
state: &SessionState,
|
||||||
|
request: oidc::HttpRequest,
|
||||||
|
) -> Result<oidc::HttpResponse, ApiClientError> {
|
||||||
|
let client = client::create_client(&state.conf.proxy, false, None, None)
|
||||||
|
.map_err(|e| e.current_context().to_owned())?;
|
||||||
|
|
||||||
|
let mut request_builder = client
|
||||||
|
.request(request.method, request.url)
|
||||||
|
.body(request.body);
|
||||||
|
for (name, value) in &request.headers {
|
||||||
|
request_builder = request_builder.header(name.as_str(), value.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = request_builder
|
||||||
|
.build()
|
||||||
|
.map_err(|_| ApiClientError::ClientConstructionFailed)?;
|
||||||
|
let response = client
|
||||||
|
.execute(request)
|
||||||
|
.await
|
||||||
|
.map_err(|_| ApiClientError::RequestNotSent("OpenIDConnect".to_string()))?;
|
||||||
|
|
||||||
|
Ok(oidc::HttpResponse {
|
||||||
|
status_code: response.status(),
|
||||||
|
headers: response.headers().to_owned(),
|
||||||
|
body: response
|
||||||
|
.bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|_| ApiClientError::ResponseDecodingFailed)?
|
||||||
|
.to_vec(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_oidc_redis_key(csrf: &str) -> String {
|
||||||
|
format!("{}OIDC_{}", consts::user::REDIS_SSO_PREFIX, csrf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_redis_connection(state: &SessionState) -> UserResult<std::sync::Arc<RedisConnectionPool>> {
|
||||||
|
state
|
||||||
|
.store
|
||||||
|
.get_redis_conn()
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Failed to get redis connection")
|
||||||
|
}
|
||||||
@ -1,12 +1,14 @@
|
|||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use api_models::user as user_api;
|
use api_models::user as user_api;
|
||||||
use common_utils::errors::CustomResult;
|
use common_utils::{errors::CustomResult, ext_traits::ValueExt};
|
||||||
use diesel_models::{enums::UserStatus, user_role::UserRole};
|
use diesel_models::{encryption::Encryption, enums::UserStatus, user_role::UserRole};
|
||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
|
use masking::{ExposeInterface, Secret};
|
||||||
use redis_interface::RedisConnectionPool;
|
use redis_interface::RedisConnectionPool;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
consts::user::{REDIS_SSO_PREFIX, REDIS_SSO_TTL},
|
||||||
core::errors::{StorageError, UserErrors, UserResult},
|
core::errors::{StorageError, UserErrors, UserResult},
|
||||||
routes::SessionState,
|
routes::SessionState,
|
||||||
services::{
|
services::{
|
||||||
@ -78,7 +80,7 @@ pub async fn generate_jwt_auth_token(
|
|||||||
state: &SessionState,
|
state: &SessionState,
|
||||||
user: &UserFromStorage,
|
user: &UserFromStorage,
|
||||||
user_role: &UserRole,
|
user_role: &UserRole,
|
||||||
) -> UserResult<masking::Secret<String>> {
|
) -> UserResult<Secret<String>> {
|
||||||
let token = AuthToken::new_token(
|
let token = AuthToken::new_token(
|
||||||
user.get_user_id().to_string(),
|
user.get_user_id().to_string(),
|
||||||
user_role.merchant_id.clone(),
|
user_role.merchant_id.clone(),
|
||||||
@ -87,7 +89,7 @@ pub async fn generate_jwt_auth_token(
|
|||||||
user_role.org_id.clone(),
|
user_role.org_id.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(masking::Secret::new(token))
|
Ok(Secret::new(token))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn generate_jwt_auth_token_with_custom_role_attributes(
|
pub async fn generate_jwt_auth_token_with_custom_role_attributes(
|
||||||
@ -96,7 +98,7 @@ pub async fn generate_jwt_auth_token_with_custom_role_attributes(
|
|||||||
merchant_id: String,
|
merchant_id: String,
|
||||||
org_id: String,
|
org_id: String,
|
||||||
role_id: String,
|
role_id: String,
|
||||||
) -> UserResult<masking::Secret<String>> {
|
) -> UserResult<Secret<String>> {
|
||||||
let token = AuthToken::new_token(
|
let token = AuthToken::new_token(
|
||||||
user.get_user_id().to_string(),
|
user.get_user_id().to_string(),
|
||||||
merchant_id,
|
merchant_id,
|
||||||
@ -105,14 +107,14 @@ pub async fn generate_jwt_auth_token_with_custom_role_attributes(
|
|||||||
org_id,
|
org_id,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(masking::Secret::new(token))
|
Ok(Secret::new(token))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_dashboard_entry_response(
|
pub fn get_dashboard_entry_response(
|
||||||
state: &SessionState,
|
state: &SessionState,
|
||||||
user: UserFromStorage,
|
user: UserFromStorage,
|
||||||
user_role: UserRole,
|
user_role: UserRole,
|
||||||
token: masking::Secret<String>,
|
token: Secret<String>,
|
||||||
) -> UserResult<user_api::DashboardEntryResponse> {
|
) -> UserResult<user_api::DashboardEntryResponse> {
|
||||||
let verification_days_left = get_verification_days_left(state, &user)?;
|
let verification_days_left = get_verification_days_left(state, &user)?;
|
||||||
|
|
||||||
@ -189,7 +191,7 @@ pub async fn get_user_from_db_by_email(
|
|||||||
.map(UserFromStorage::from)
|
.map(UserFromStorage::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_token_from_signin_response(resp: &user_api::SignInResponse) -> masking::Secret<String> {
|
pub fn get_token_from_signin_response(resp: &user_api::SignInResponse) -> Secret<String> {
|
||||||
match resp {
|
match resp {
|
||||||
user_api::SignInResponse::DashboardEntry(data) => data.token.clone(),
|
user_api::SignInResponse::DashboardEntry(data) => data.token.clone(),
|
||||||
user_api::SignInResponse::MerchantSelect(data) => data.token.clone(),
|
user_api::SignInResponse::MerchantSelect(data) => data.token.clone(),
|
||||||
@ -213,3 +215,74 @@ impl ForeignFrom<user_api::AuthConfig> for common_enums::UserAuthType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn decrypt_oidc_private_config(
|
||||||
|
state: &SessionState,
|
||||||
|
encrypted_config: Option<Encryption>,
|
||||||
|
) -> UserResult<user_api::OpenIdConnectPrivateConfig> {
|
||||||
|
let user_auth_key = hex::decode(
|
||||||
|
state
|
||||||
|
.conf
|
||||||
|
.user_auth_methods
|
||||||
|
.get_inner()
|
||||||
|
.encryption_key
|
||||||
|
.clone()
|
||||||
|
.expose(),
|
||||||
|
)
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Failed to decode DEK")?;
|
||||||
|
|
||||||
|
let private_config = domain::types::decrypt::<serde_json::Value, masking::WithType>(
|
||||||
|
encrypted_config,
|
||||||
|
&user_auth_key,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Failed to decrypt private config")?
|
||||||
|
.ok_or(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Private config not found")?
|
||||||
|
.into_inner()
|
||||||
|
.expose();
|
||||||
|
|
||||||
|
private_config
|
||||||
|
.parse_value("OpenIdConnectPrivateConfig")
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("unable to parse OpenIdConnectPrivateConfig")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_sso_id_in_redis(
|
||||||
|
state: &SessionState,
|
||||||
|
oidc_state: Secret<String>,
|
||||||
|
sso_id: String,
|
||||||
|
) -> UserResult<()> {
|
||||||
|
let connection = get_redis_connection(state)?;
|
||||||
|
let key = get_oidc_key(&oidc_state.expose());
|
||||||
|
connection
|
||||||
|
.set_key_with_expiry(&key, sso_id, REDIS_SSO_TTL)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Failed to set sso id in redis")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_sso_id_from_redis(
|
||||||
|
state: &SessionState,
|
||||||
|
oidc_state: Secret<String>,
|
||||||
|
) -> UserResult<String> {
|
||||||
|
let connection = get_redis_connection(state)?;
|
||||||
|
let key = get_oidc_key(&oidc_state.expose());
|
||||||
|
connection
|
||||||
|
.get_key::<Option<String>>(&key)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("Failed to get sso id from redis")?
|
||||||
|
.ok_or(UserErrors::SSOFailed)
|
||||||
|
.attach_printable("Cannot find oidc state in redis. Oidc state invalid or expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_oidc_key(oidc_state: &str) -> String {
|
||||||
|
format!("{}{oidc_state}", REDIS_SSO_PREFIX)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_oidc_sso_redirect_url(state: &SessionState, provider: &str) -> String {
|
||||||
|
format!("{}/redirect/oidc/{}", state.conf.user.base_url, provider)
|
||||||
|
}
|
||||||
|
|||||||
@ -432,6 +432,10 @@ pub enum Flow {
|
|||||||
UpdateUserAuthenticationMethod,
|
UpdateUserAuthenticationMethod,
|
||||||
// List user authentication methods
|
// List user authentication methods
|
||||||
ListUserAuthenticationMethods,
|
ListUserAuthenticationMethods,
|
||||||
|
/// Get sso auth url
|
||||||
|
GetSsoAuthUrl,
|
||||||
|
/// Signin with SSO
|
||||||
|
SignInWithSso,
|
||||||
/// List initial webhook delivery attempts
|
/// List initial webhook delivery attempts
|
||||||
WebhookEventInitialDeliveryAttemptList,
|
WebhookEventInitialDeliveryAttemptList,
|
||||||
/// List delivery attempts for a webhook event
|
/// List delivery attempts for a webhook event
|
||||||
|
|||||||
Reference in New Issue
Block a user