Skip to content

Commit

Permalink
fix: Onboarding & other UI fixes, security improvements (#2036)
Browse files Browse the repository at this point in the history
Co-authored-by: Štěpán Granát <granat.stepan@gmail.com>
  • Loading branch information
JanCizmar and stepan662 committed Dec 22, 2023
1 parent 1a315c9 commit 8cde2c5
Show file tree
Hide file tree
Showing 29 changed files with 288 additions and 216 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import io.tolgee.configuration.tolgee.GithubAuthenticationProperties
import io.tolgee.configuration.tolgee.TolgeeProperties
import io.tolgee.constants.Message
import io.tolgee.exceptions.AuthenticationException
import io.tolgee.model.Invitation
import io.tolgee.model.UserAccount
import io.tolgee.security.authentication.JwtService
import io.tolgee.security.payload.JwtAuthenticationResponse
import io.tolgee.service.InvitationService
import io.tolgee.service.security.SignUpService
import io.tolgee.service.security.UserAccountService
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
Expand All @@ -24,8 +23,8 @@ class GithubOAuthDelegate(
private val jwtService: JwtService,
private val userAccountService: UserAccountService,
private val restTemplate: RestTemplate,
private val properties: TolgeeProperties,
private val invitationService: InvitationService
properties: TolgeeProperties,
private val signUpService: SignUpService
) {
private val githubConfigurationProperties: GithubAuthenticationProperties = properties.authentication.github

Expand Down Expand Up @@ -72,25 +71,15 @@ class GithubOAuthDelegate(
throw AuthenticationException(Message.USERNAME_ALREADY_EXISTS)
}

var invitation: Invitation? = null
if (invitationCode == null) {
if (!properties.authentication.registrationsAllowed) {
throw AuthenticationException(Message.REGISTRATIONS_NOT_ALLOWED)
}
} else {
invitation = invitationService.getInvitation(invitationCode)
}

val newUserAccount = UserAccount()
newUserAccount.username = githubEmail
newUserAccount.name = userResponse.name ?: userResponse.login
newUserAccount.thirdPartyAuthId = userResponse.id
newUserAccount.thirdPartyAuthType = "github"
newUserAccount.accountType = UserAccount.AccountType.THIRD_PARTY
userAccountService.createUser(newUserAccount)
if (invitation != null) {
invitationService.accept(invitation.code, newUserAccount)
}

signUpService.signUp(newUserAccount, invitationCode, null)

newUserAccount
}
val jwt = jwtService.emitToken(user.id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import io.tolgee.configuration.tolgee.GoogleAuthenticationProperties
import io.tolgee.configuration.tolgee.TolgeeProperties
import io.tolgee.constants.Message
import io.tolgee.exceptions.AuthenticationException
import io.tolgee.model.Invitation
import io.tolgee.model.UserAccount
import io.tolgee.security.authentication.JwtService
import io.tolgee.security.payload.JwtAuthenticationResponse
import io.tolgee.service.InvitationService
import io.tolgee.service.security.SignUpService
import io.tolgee.service.security.UserAccountService
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
Expand All @@ -23,8 +22,8 @@ class GoogleOAuthDelegate(
private val jwtService: JwtService,
private val userAccountService: UserAccountService,
private val restTemplate: RestTemplate,
private val properties: TolgeeProperties,
private val invitationService: InvitationService
properties: TolgeeProperties,
private val signUpService: SignUpService
) {
private val googleConfigurationProperties: GoogleAuthenticationProperties = properties.authentication.google

Expand Down Expand Up @@ -78,26 +77,14 @@ class GoogleOAuthDelegate(
throw AuthenticationException(Message.USERNAME_ALREADY_EXISTS)
}

var invitation: Invitation? = null
if (invitationCode == null) {
if (!properties.authentication.registrationsAllowed) {
throw AuthenticationException(Message.REGISTRATIONS_NOT_ALLOWED)
}
} else {
invitation = invitationService.getInvitation(invitationCode)
}

val newUserAccount = UserAccount()
newUserAccount.username = userResponse.email
?: throw AuthenticationException(Message.THIRD_PARTY_AUTH_NO_EMAIL)
newUserAccount.name = userResponse.name ?: (userResponse.given_name + " " + userResponse.family_name)
newUserAccount.thirdPartyAuthId = userResponse.sub
newUserAccount.thirdPartyAuthType = "google"
newUserAccount.accountType = UserAccount.AccountType.THIRD_PARTY
userAccountService.createUser(newUserAccount)
if (invitation != null) {
invitationService.accept(invitation.code, newUserAccount)
}
signUpService.signUp(newUserAccount, invitationCode, null)

newUserAccount
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import io.tolgee.configuration.tolgee.OAuth2AuthenticationProperties
import io.tolgee.configuration.tolgee.TolgeeProperties
import io.tolgee.constants.Message
import io.tolgee.exceptions.AuthenticationException
import io.tolgee.model.Invitation
import io.tolgee.model.UserAccount
import io.tolgee.security.authentication.JwtService
import io.tolgee.security.payload.JwtAuthenticationResponse
import io.tolgee.service.InvitationService
import io.tolgee.service.security.SignUpService
import io.tolgee.service.security.UserAccountService
import org.slf4j.LoggerFactory
import org.springframework.http.HttpEntity
Expand All @@ -27,8 +26,8 @@ class OAuth2Delegate(
private val jwtService: JwtService,
private val userAccountService: UserAccountService,
private val restTemplate: RestTemplate,
private val properties: TolgeeProperties,
private val invitationService: InvitationService
properties: TolgeeProperties,
private val signUpService: SignUpService
) {
private val oauth2ConfigurationProperties: OAuth2AuthenticationProperties = properties.authentication.oauth2
private val logger = LoggerFactory.getLogger(this::class.java)
Expand Down Expand Up @@ -96,15 +95,6 @@ class OAuth2Delegate(
throw AuthenticationException(Message.USERNAME_ALREADY_EXISTS)
}

var invitation: Invitation? = null
if (invitationCode == null) {
if (!properties.authentication.registrationsAllowed) {
throw AuthenticationException(Message.REGISTRATIONS_NOT_ALLOWED)
}
} else {
invitation = invitationService.getInvitation(invitationCode)
}

val newUserAccount = UserAccount()
newUserAccount.username =
userResponse.email ?: throw AuthenticationException(Message.THIRD_PARTY_AUTH_NO_EMAIL)
Expand All @@ -120,10 +110,8 @@ class OAuth2Delegate(
newUserAccount.thirdPartyAuthId = userResponse.sub
newUserAccount.thirdPartyAuthType = "oauth2"
newUserAccount.accountType = UserAccount.AccountType.THIRD_PARTY
userAccountService.createUser(newUserAccount)
if (invitation != null) {
invitationService.accept(invitation.code, newUserAccount)
}
signUpService.signUp(newUserAccount, invitationCode, null)

newUserAccount
}
val jwt = jwtService.emitToken(user.id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package io.tolgee.commandLineRunners

import io.tolgee.configuration.tolgee.InternalProperties
import io.tolgee.configuration.tolgee.TolgeeProperties
import io.tolgee.dtos.request.auth.SignUpDto
import io.tolgee.dtos.request.organization.OrganizationDto
import io.tolgee.model.UserAccount
import io.tolgee.security.InitialPasswordManager
Expand Down Expand Up @@ -45,10 +44,29 @@ class InitialUserCreatorCommandLineRunner(
fun createInitialUser() {
logger.info("Creating initial user...")
val initialUsername = properties.authentication.initialUsername

// Check if the account already exists.
// This can only be the case on Tolgee 3.x series and should be removed on Tolgee 4.
val candidate = userAccountService.findActive(initialUsername)
if (candidate != null) {
candidate.isInitialUser = true
userAccountService.save(candidate)
return
}

val initialPassword = initialPasswordManager.initialPassword
val user = userAccountService.createInitialUser(
SignUpDto(email = initialUsername, password = initialPassword, name = initialUsername),
)
val user = UserAccount(
username = initialUsername,
password = passwordEncoder.encode(initialPassword),
name = initialUsername,
role = UserAccount.Role.ADMIN,
).apply {
passwordChanged = false
isInitialUser = true
}

userAccountService.createUser(userAccount = user)
userAccountService.transferLegacyNoAuthUser()

// If the user was already existing, it may already have assigned orgs.
// To avoid conflicts, we only create the org if the user doesn't have any.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class DemoProjectCreator(
val project = Project().apply {
name = "Demo project"
this@apply.organizationOwner = organization
this.description = "This is a demo project of an packing list app"
this.description = "This is a demo project of a packing list app"
}
projectService.save(project)
setAvatar(project)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package io.tolgee.development

import io.tolgee.configuration.tolgee.TolgeeProperties
import io.tolgee.dtos.request.LanguageDto
import io.tolgee.dtos.request.auth.SignUpDto
import io.tolgee.dtos.request.organization.OrganizationDto
import io.tolgee.model.ApiKey
import io.tolgee.model.Language
Expand All @@ -25,6 +24,7 @@ import io.tolgee.service.security.UserAccountService
import io.tolgee.util.SlugGenerator
import io.tolgee.util.executeInNewTransaction
import jakarta.persistence.EntityManager
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.annotation.Transactional
Expand All @@ -45,6 +45,7 @@ class DbPopulatorReal(
private val apiKeyService: ApiKeyService,
private val languageStatsService: LanguageStatsService,
private val platformTransactionManager: PlatformTransactionManager,
private val passwordEncoder: PasswordEncoder
) {
private lateinit var de: Language
private lateinit var en: Language
Expand All @@ -59,12 +60,17 @@ class DbPopulatorReal(

fun createUserIfNotExists(username: String, password: String? = null, name: String? = null): UserAccount {
return userAccountService.findActive(username) ?: let {
val signUpDto = SignUpDto(
name = name ?: username, email = username,
password = password
?: initialPasswordManager.initialPassword

val rawPassword = password
?: initialPasswordManager.initialPassword

userAccountService.createUser(
UserAccount(
name = name ?: username,
username = username,
password = passwordEncoder.encode(rawPassword)
)
)
userAccountService.createUser(signUpDto)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,20 @@ import io.tolgee.service.organization.OrganizationRoleService
import io.tolgee.service.security.PermissionService
import io.tolgee.util.Logging
import org.apache.commons.lang3.RandomStringUtils
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.Duration
import java.time.Instant
import java.util.*

@Service
class InvitationService @Autowired constructor(
class InvitationService(
private val invitationRepository: InvitationRepository,
private val authenticationFacade: AuthenticationFacade,
private val organizationRoleService: OrganizationRoleService,
private val permissionService: PermissionService,
private val invitationEmailSender: InvitationEmailSender,
private val businessEventPublisher: BusinessEventPublisher
private val businessEventPublisher: BusinessEventPublisher,
) : Logging {
@Transactional
fun create(params: CreateProjectInvitationParams): Invitation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ package io.tolgee.service.security
import io.tolgee.configuration.tolgee.TolgeeProperties
import io.tolgee.constants.Message
import io.tolgee.dtos.request.auth.SignUpDto
import io.tolgee.exceptions.AuthenticationException
import io.tolgee.exceptions.BadRequestException
import io.tolgee.model.Invitation
import io.tolgee.model.UserAccount
import io.tolgee.security.authentication.JwtService
import io.tolgee.security.payload.JwtAuthenticationResponse
import io.tolgee.service.EmailVerificationService
import io.tolgee.service.InvitationService
import io.tolgee.service.QuickStartService
import io.tolgee.service.organization.OrganizationService
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

Expand All @@ -22,39 +25,56 @@ class SignUpService(
private val jwtService: JwtService,
private val emailVerificationService: EmailVerificationService,
private val organizationService: OrganizationService,
private val quickStartService: QuickStartService
private val quickStartService: QuickStartService,
private val passwordEncoder: PasswordEncoder
) {
@Transactional
fun signUp(dto: SignUpDto): JwtAuthenticationResponse? {
var invitation: Invitation? = null
if (dto.invitationCode == null) {
tolgeeProperties.authentication.checkAllowedRegistrations()
} else {
invitation = invitationService.getInvitation(dto.invitationCode) // it throws an exception
}

userAccountService.findActive(dto.email)?.let {
throw BadRequestException(Message.USERNAME_ALREADY_EXISTS)
}

val user = userAccountService.createUser(dto)
val user = dtoToEntity(dto)
signUp(user, dto.invitationCode, dto.organizationName)

if (!tolgeeProperties.authentication.needsEmailVerification) {
return JwtAuthenticationResponse(jwtService.emitToken(user.id, true))
}

emailVerificationService.createForUser(user, dto.callbackUrl)

return null
}

fun signUp(entity: UserAccount, invitationCode: String?, organizationName: String?): UserAccount {
val invitation = findAndCheckInvitationOnRegistration(invitationCode)
val user = userAccountService.createUser(entity)
if (invitation != null) {
invitationService.accept(invitation.code, user)
}

val canCreateOrganization = tolgeeProperties.authentication.userCanCreateOrganizations
if (canCreateOrganization && (invitation == null || !dto.organizationName.isNullOrBlank())) {
val name = if (dto.organizationName.isNullOrBlank()) user.name else dto.organizationName!!
if (canCreateOrganization && (invitation == null || !organizationName.isNullOrBlank())) {
val name = if (organizationName.isNullOrBlank()) user.name else organizationName
val organization = organizationService.createPreferred(user, name)
quickStartService.create(user, organization)
}
return user
}

if (!tolgeeProperties.authentication.needsEmailVerification) {
return JwtAuthenticationResponse(jwtService.emitToken(user.id, true))
}

emailVerificationService.createForUser(user, dto.callbackUrl)
fun dtoToEntity(request: SignUpDto): UserAccount {
val encodedPassword = passwordEncoder.encode(request.password!!)
return UserAccount(name = request.name, username = request.email, password = encodedPassword)
}

return null
@Transactional
fun findAndCheckInvitationOnRegistration(invitationCode: String?): Invitation? {
if (invitationCode == null) {
if (!tolgeeProperties.authentication.registrationsAllowed) {
throw AuthenticationException(Message.REGISTRATIONS_NOT_ALLOWED)
}
return null
}
return invitationService.getInvitation(invitationCode)
}
}
Loading

0 comments on commit 8cde2c5

Please sign in to comment.