Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: third party authentication provider change #2929

Draft
wants to merge 57 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
8b81b2b
feat: wip: third party authentication provider change
Anty0 Feb 20, 2025
6825fe1
fix: tests build + api refractor
Anty0 Feb 20, 2025
be0b889
chore: eslint
Anty0 Feb 24, 2025
8f88f8a
fix: avoid caching organization db object as part of tenant
Anty0 Feb 24, 2025
6ec66c4
chore: add required mocking for sso and email checks
Anty0 Feb 24, 2025
99af3ee
fix: separate email valid and sso checks to separate interceptors
Anty0 Feb 24, 2025
ae3c540
fix: authentication interceptors tests
Anty0 Feb 24, 2025
d81a12b
chore: lint
Anty0 Feb 24, 2025
170ca01
fix: propely mock properties
Anty0 Feb 24, 2025
4a0d79e
fix: exclude internal endpoints for email and sso checks
Anty0 Feb 24, 2025
011764b
fix: use correct exception for failed sso check
Anty0 Feb 24, 2025
68ae605
feat: initial frontend provider configuration page and routing
Anty0 Feb 25, 2025
a5fa7ce
fix: lint
Anty0 Feb 25, 2025
e65fed6
fix: mart swagger api-docs endpoint as public
Anty0 Feb 25, 2025
5ad1d5b
fix: add avatars to public endpoints
Anty0 Feb 25, 2025
8522722
fix: use proxy for calling save to trigger cache invalidation properly
Anty0 Feb 25, 2025
585aaa3
fix: include domain in private user model and user account dto
Anty0 Feb 25, 2025
5aa566b
fix: expose more auth info on backend; finalize fe ui prototype
Anty0 Feb 25, 2025
113d819
fix: unused parameter
Anty0 Feb 25, 2025
ef74d45
fix: mock properly user domain
Anty0 Feb 26, 2025
803925c
fix: don't thow when no sso configuration - fixes initial data
Anty0 Feb 26, 2025
8e7c9f5
feat: redirect to sso migration screen when migration required
Anty0 Feb 26, 2025
aa6387a
chore: lint
Anty0 Feb 26, 2025
eb0273c
fix: avoid not found exception for sso info
Anty0 Feb 26, 2025
a13c07b
fix: revert - allow accountType in UserAccountView to be null - can h…
Anty0 Feb 26, 2025
067a13c
chore: lint
Anty0 Feb 26, 2025
3b43fc8
feat: implement sso migration ui prototype
Anty0 Feb 26, 2025
215af24
fix: fix remaining issue and finalize implementation
Anty0 Feb 27, 2025
fe400c9
fix: allow key extraction by moving the condition
Anty0 Feb 27, 2025
bcf88f4
fix: remove url load error text for now
Anty0 Feb 27, 2025
516afc9
feat: disallow sign up for sso configured domains - force sso affects…
Anty0 Feb 28, 2025
aa74c95
feat: allow changing sso domain only by admins
Anty0 Feb 28, 2025
6bd4b71
feat: remove domain whitelist
Anty0 Feb 28, 2025
7793cdf
feat: finalize sso migration and provider change dialog design and text
Anty0 Feb 28, 2025
685aa6d
chore: lint
Anty0 Feb 28, 2025
21f5298
feat: show who manages user in user account settings
Anty0 Feb 28, 2025
7a11790
fix: sso tests build
Anty0 Mar 1, 2025
96bdff7
fix: better help text for sso domain
Anty0 Mar 3, 2025
055fba7
fix: move managedBy out of initial-data
Anty0 Mar 3, 2025
6b1d915
fix: avoid passing null to cacheable function
Anty0 Mar 3, 2025
1983ba4
fix: sso organizations tests
Anty0 Mar 3, 2025
c81a685
fix: dont require sso feature to refresh user sso token
Anty0 Mar 3, 2025
50ca862
fix: switch back to admin account when receiving unauthorized error w…
Anty0 Mar 3, 2025
52841bb
fix: global sso authentication - global sso doesn't require user to b…
Anty0 Mar 3, 2025
42ab569
fix: e2e upload avatar test - avoid remounting user profile fields
Anty0 Mar 3, 2025
9fad378
fix: avoid loop when handling oauth redirect
Anty0 Mar 3, 2025
d91fe66
fix: explicitly redirect user to next page when oauth login succeeds
Anty0 Mar 3, 2025
f53bbbe
fix: add missing error message translations
Anty0 Mar 4, 2025
fba9084
fix: notifications - avoid causing redirects + auto accept forced sso…
Anty0 Mar 4, 2025
619cc57
fix: make sure account settings work during email verification
Anty0 Mar 4, 2025
c06dfd4
chore: refractor sso login e2e
Anty0 Mar 4, 2025
32186f2
chore: native signup tests
Anty0 Mar 4, 2025
cac0c0f
chore: left over line
Anty0 Mar 5, 2025
e41030e
fix: finalize third-party change ui in settings
Anty0 Mar 5, 2025
490fe0c
fix: implement auth provider change contreoller tests + fixes
Anty0 Mar 6, 2025
3d72675
fix: tests
Anty0 Mar 6, 2025
82500ff
fix: public controller auth change tests - test initiate, switch, aut…
Anty0 Mar 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import io.tolgee.api.EeSubscriptionProvider
import io.tolgee.component.PreferredOrganizationFacade
import io.tolgee.hateoas.InitialDataModel
import io.tolgee.hateoas.ee.IEeSubscriptionModelAssembler
import io.tolgee.hateoas.sso.PublicSsoTenantModelAssembler
import io.tolgee.hateoas.userAccount.PrivateUserAccountModelAssembler
import io.tolgee.openApiDocs.OpenApiHideFromPublicDocs
import io.tolgee.security.authentication.AuthenticationFacade
import io.tolgee.service.TenantService
import io.tolgee.service.security.UserPreferencesService
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.GetMapping
Expand All @@ -26,14 +29,16 @@ import org.springframework.web.bind.annotation.RestController
class InitialDataController(
private val configurationController: ConfigurationController,
private val authenticationFacade: AuthenticationFacade,
private val userController: V2UserController,
private val userPreferencesService: UserPreferencesService,
private val preferredOrganizationFacade: PreferredOrganizationFacade,
@Suppress("SpringJavaInjectionPointsAutowiringInspection")
private val eeSubscriptionModelAssembler: IEeSubscriptionModelAssembler?,
@Suppress("SpringJavaInjectionPointsAutowiringInspection")
private val eeSubscriptionProvider: EeSubscriptionProvider?,
private val announcementController: AnnouncementController,
private val tenantService: TenantService,
private val privateUserAccountModelAssembler: PrivateUserAccountModelAssembler,
private val publicSsoTenantModelAssembler: PublicSsoTenantModelAssembler,
) : IController {
@GetMapping(value = [""])
@Operation(summary = "Get initial data", description = "Returns initial data required by the UI to load")
Expand All @@ -51,7 +56,10 @@ class InitialDataController(

val userAccount = authenticationFacade.authenticatedUserOrNull
if (userAccount != null) {
data.userInfo = userController.getInfo()
val userAccountView = authenticationFacade.authenticatedUserView
val tenant = tenantService.getEnabledConfigByDomainOrNull(userAccount.domain)
data.userInfo = privateUserAccountModelAssembler.toModel(userAccountView)
data.ssoInfo = tenant?.let { publicSsoTenantModelAssembler.toModel(it) }
data.preferredOrganization = preferredOrganizationFacade.getPreferred()
data.languageTag = userPreferencesService.find(userAccount.id)?.language
data.announcement = announcementController.getLatest()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import io.tolgee.model.Pat
import io.tolgee.security.authentication.AllowApiAccess
import io.tolgee.security.authentication.AuthTokenType
import io.tolgee.security.authentication.AuthenticationFacade
import io.tolgee.security.authentication.BypassEmailVerification
import io.tolgee.security.authentication.BypassForcedSsoAuthentication
import io.tolgee.security.authentication.RequiresSuperAuthentication
import io.tolgee.service.security.PatService
import jakarta.validation.Valid
Expand Down Expand Up @@ -50,6 +52,8 @@ class PatController(
) : IController {
@GetMapping(value = [""])
@Operation(summary = "Get PAKs")
@BypassEmailVerification
@BypassForcedSsoAuthentication
fun getAll(
@ParameterObject pageable: Pageable,
): PagedModel<PatModel> {
Expand All @@ -61,6 +65,8 @@ class PatController(

@GetMapping(value = ["/{id:[0-9]+}"])
@Operation(summary = "Get one PAK")
@BypassEmailVerification
@BypassForcedSsoAuthentication
fun get(
@PathVariable id: Long,
): PatModel {
Expand Down Expand Up @@ -111,6 +117,8 @@ class PatController(
@DeleteMapping(value = ["/{id:[0-9]+}"])
@Operation(summary = "Delete PAK", description = "Deletes Personal Access Token")
@RequiresSuperAuthentication
@BypassEmailVerification
@BypassForcedSsoAuthentication
fun delete(
@PathVariable id: Long,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import io.tolgee.component.reporting.OnBusinessEventToCaptureEvent
import io.tolgee.hateoas.quickStart.QuickStartModel
import io.tolgee.hateoas.quickStart.QuickStartModelAssembler
import io.tolgee.security.authentication.AuthenticationFacade
import io.tolgee.security.authentication.BypassEmailVerification
import io.tolgee.security.authentication.BypassForcedSsoAuthentication
import io.tolgee.service.QuickStartService
import org.springframework.data.crossstore.ChangeSetPersister.NotFoundException
import org.springframework.web.bind.annotation.PathVariable
Expand All @@ -25,6 +27,8 @@ class QuickStartController(
) {
@PutMapping("/steps/{step}/complete")
@Operation(summary = "Complete guide step", description = "Marks guide step as completed")
@BypassEmailVerification
@BypassForcedSsoAuthentication
fun completeGuideStep(
@PathVariable("step") step: String,
): QuickStartModel {
Expand All @@ -42,6 +46,8 @@ class QuickStartController(

@PutMapping("/set-finished/{finished}")
@Operation(summary = "Set finished state", description = "Sets finished state of the quick start guide")
@BypassEmailVerification
@BypassForcedSsoAuthentication
fun setFinishedState(
@PathVariable finished: Boolean,
): QuickStartModel {
Expand All @@ -57,6 +63,8 @@ class QuickStartController(

@PutMapping("/set-open/{open}")
@Operation(summary = "Set open state", description = "Sets open state of the quick start guide")
@BypassEmailVerification
@BypassForcedSsoAuthentication
fun setOpenState(
@PathVariable open: Boolean,
): QuickStartModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.tolgee.dtos.request.UserMfaRecoveryRequestDto
import io.tolgee.dtos.request.UserTotpDisableRequestDto
import io.tolgee.dtos.request.UserTotpEnableRequestDto
import io.tolgee.security.authentication.AuthenticationFacade
import io.tolgee.security.authentication.BypassEmailVerification
import io.tolgee.security.authentication.JwtService
import io.tolgee.security.payload.JwtAuthenticationResponse
import io.tolgee.service.security.MfaService
Expand All @@ -29,6 +30,7 @@ class UserMfaController(
summary = "Enable TOTP",
description = "Enables TOTP-based two-factor authentication. Invalidates all previous sessions upon success.",
)
@BypassEmailVerification
fun enableMfa(
@RequestBody @Valid
dto: UserTotpEnableRequestDto,
Expand All @@ -44,6 +46,7 @@ class UserMfaController(
summary = "Disable TOTP",
description = "Disables TOTP-based two-factor authentication. Invalidates all previous sessions upon success.",
)
@BypassEmailVerification
fun disableMfa(
@RequestBody @Valid
dto: UserTotpDisableRequestDto,
Expand All @@ -56,6 +59,7 @@ class UserMfaController(

@PutMapping("/recovery")
@Operation(summary = "Regenerate Codes", description = "Regenerates multi-factor authentication recovery codes")
@BypassEmailVerification
fun regenerateRecoveryCodes(
@RequestBody @Valid
dto: UserMfaRecoveryRequestDto,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import io.tolgee.hateoas.userPreferences.UserPreferencesModel
import io.tolgee.security.authentication.AuthenticationFacade
import io.tolgee.security.authentication.BypassEmailVerification
import io.tolgee.security.authentication.BypassForcedSsoAuthentication
import io.tolgee.service.organization.OrganizationRoleService
import io.tolgee.service.organization.OrganizationService
import io.tolgee.service.security.UserPreferencesService
Expand All @@ -30,6 +32,8 @@ class UserPreferencesController(
) {
@GetMapping("")
@Operation(summary = "Get user's preferences")
@BypassEmailVerification
@BypassForcedSsoAuthentication
fun get(): UserPreferencesModel {
return userPreferencesService.findOrCreate(authenticationFacade.authenticatedUser.id).let {
UserPreferencesModel(language = it.language, preferredOrganizationId = it.preferredOrganization?.id)
Expand All @@ -38,6 +42,8 @@ class UserPreferencesController(

@PutMapping("/set-language/{languageTag}")
@Operation(summary = "Set user's UI language")
@BypassEmailVerification
@BypassForcedSsoAuthentication
fun setLanguage(
@PathVariable languageTag: String,
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
package io.tolgee.api.v2.controllers

import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.tags.Tag
import io.tolgee.activity.ActivityHolder
import io.tolgee.component.enabledFeaturesProvider.EnabledFeaturesProvider
import io.tolgee.constants.Message
import io.tolgee.dtos.request.SuperTokenRequest
import io.tolgee.dtos.request.UserUpdatePasswordRequestDto
import io.tolgee.dtos.request.UserUpdateRequestDto
import io.tolgee.exceptions.AuthenticationException
import io.tolgee.hateoas.organization.PrivateOrganizationModel
import io.tolgee.hateoas.organization.PrivateOrganizationModelAssembler
import io.tolgee.hateoas.organization.SimpleOrganizationModel
import io.tolgee.hateoas.organization.SimpleOrganizationModelAssembler
import io.tolgee.hateoas.sso.PublicSsoTenantModel
import io.tolgee.hateoas.sso.PublicSsoTenantModelAssembler
import io.tolgee.hateoas.userAccount.PrivateUserAccountModel
import io.tolgee.hateoas.userAccount.PrivateUserAccountModelAssembler
import io.tolgee.openApiDocs.OpenApiHideFromPublicDocs
import io.tolgee.openApiDocs.OpenApiOrderExtension
import io.tolgee.security.authentication.AllowApiAccess
import io.tolgee.security.authentication.AuthenticationFacade
import io.tolgee.security.authentication.BypassEmailVerification
import io.tolgee.security.authentication.BypassForcedSsoAuthentication
import io.tolgee.security.authentication.JwtService
import io.tolgee.security.authentication.RequiresSuperAuthentication
import io.tolgee.security.payload.JwtAuthenticationResponse
import io.tolgee.service.EmailVerificationService
import io.tolgee.service.ImageUploadService
import io.tolgee.service.TenantService
import io.tolgee.service.organization.OrganizationRoleService
import io.tolgee.service.organization.OrganizationService
import io.tolgee.service.security.MfaService
import io.tolgee.service.security.UserAccountService
Expand All @@ -42,12 +52,17 @@ class V2UserController(
private val authenticationFacade: AuthenticationFacade,
private val userAccountService: UserAccountService,
private val privateUserAccountModelAssembler: PrivateUserAccountModelAssembler,
private val publicSsoTenantModelAssembler: PublicSsoTenantModelAssembler,
private val imageUploadService: ImageUploadService,
private val organizationService: OrganizationService,
private val organizationRoleService: OrganizationRoleService,
private val privateOrganizationModelAssembler: PrivateOrganizationModelAssembler,
private val tenantService: TenantService,
private val simpleOrganizationModelAssembler: SimpleOrganizationModelAssembler,
private val passwordEncoder: PasswordEncoder,
private val jwtService: JwtService,
private val mfaService: MfaService,
private val enabledFeaturesProvider: EnabledFeaturesProvider,
private val emailVerificationService: EmailVerificationService,
@Qualifier("requestActivityHolder") private val request: ActivityHolder,
) {
Expand All @@ -56,6 +71,7 @@ class V2UserController(
description = "Resends email verification email to currently authenticated user.",
)
@PostMapping("/send-email-verification")
@BypassEmailVerification
fun sendEmailVerification(request: HttpServletRequest) {
val user = authenticationFacade.authenticatedUserEntity
emailVerificationService.resendEmailVerification(user, request)
Expand All @@ -66,6 +82,8 @@ class V2UserController(
description = "Returns information about currently authenticated user.",
)
@GetMapping("")
@BypassEmailVerification
@BypassForcedSsoAuthentication
@AllowApiAccess
@OpenApiOrderExtension(1)
fun getInfo(): PrivateUserAccountModel {
Expand Down Expand Up @@ -141,6 +159,51 @@ class V2UserController(
userAccountService.delete(authenticationFacade.authenticatedUserEntity)
}

@Operation(
summary = "Get information about SSO configuration",
description = "Returns information about sso configuration affecting the user.",
)
@GetMapping("/sso")
@ApiResponse(
responseCode = "204",
description = "No SSO configuration available for this user",
)
@BypassEmailVerification
@BypassForcedSsoAuthentication
@AllowApiAccess
fun getSso(): ResponseEntity<PublicSsoTenantModel> {
val userAccount = authenticationFacade.authenticatedUser
val domain = userAccount.domain ?: return ResponseEntity.noContent().build()
val tenant = tenantService.getEnabledConfigByDomainOrNull(domain) ?: return ResponseEntity.noContent().build()
return ResponseEntity.ok(publicSsoTenantModelAssembler.toModel(tenant))
}

@GetMapping("/managed-by")
@Operation(
summary = "Get organization which manages user",
description = "Returns the organization that manages a given user or null",
)
@ApiResponse(
responseCode = "204",
description = "No SSO configuration available for this user",
)
@BypassEmailVerification
@BypassForcedSsoAuthentication
@OpenApiHideFromPublicDocs
fun getManagedBy(): ResponseEntity<PrivateOrganizationModel> {
val userAccount = authenticationFacade.authenticatedUser
val org = organizationRoleService.getManagedBy(userId = userAccount.id) ?: return ResponseEntity.noContent().build()
val view =
organizationService.findPrivateView(org.id, authenticationFacade.authenticatedUser.id)
?: return ResponseEntity.noContent().build()
return ResponseEntity.ok(
privateOrganizationModelAssembler.toModel(
view,
enabledFeaturesProvider.get(view.organization.id),
),
)
}

@PostMapping("")
@Operation(summary = "Updates current user's data.", deprecated = true)
@OpenApiHideFromPublicDocs
Expand All @@ -163,6 +226,8 @@ class V2UserController(

@PostMapping("/generate-super-token")
@Operation(summary = "Get super JWT", description = "Generates new JWT token permitted to sensitive operations")
@BypassEmailVerification
@BypassForcedSsoAuthentication
fun getSuperToken(
@RequestBody @Valid
req: SuperTokenRequest,
Expand Down
Loading
Loading