Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -1,7 +1,17 @@
package co.nilin.opex.auth.gateway.data

data class ChangePasswordRequest(
val password: String,
val newPassword: String,
val confirmation: String,
)
class ChangePasswordRequest{

var password: String?=null
var newPassword: String?=null
var confirmation: String?=null

constructor()

constructor(password: String?, newPassword: String?, confirmation: String?) {
this.password = password
this.newPassword = newPassword
this.confirmation = confirmation
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package co.nilin.opex.auth.gateway.data

data class UserSessionResponse(
val id: String,
val ipAddress: String,
val started: Int,
val lastAccess: Int,
val started: Long,
val lastAccess: Long,
val state: String,
val inUse:Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import org.keycloak.models.KeycloakSession
import org.keycloak.models.UserCredentialModel
import org.keycloak.models.UserModel
import org.keycloak.models.credential.OTPCredentialModel
import org.keycloak.models.utils.CredentialValidation
import org.keycloak.models.utils.HmacOTP
import org.keycloak.services.managers.AuthenticationManager
import org.keycloak.services.resource.RealmResourceProvider
import org.keycloak.services.resources.LoginActionsService
import org.keycloak.utils.CredentialHelper
Expand Down Expand Up @@ -125,13 +127,11 @@ class UserManagementResource(private val session: KeycloakSession) : RealmResour
val user = session.users().getUserById(auth.getUserId(), opexRealm) ?: return ErrorHandler.userNotFound()

val cred = UserCredentialModel.password(body.password)
if (!session.userCredentialManager()
.isValid(opexRealm, user, cred)
) return ErrorHandler.response(Response.Status.FORBIDDEN, OpexError.Forbidden, "Incorrect password")
if (!session.userCredentialManager().isValid(opexRealm, user, cred))
return ErrorHandler.forbidden("Incorrect password")

if (body.confirmation == body.newPassword) return ErrorHandler.response(
Response.Status.BAD_REQUEST, OpexError.BadRequest, "Invalid password confirmation"
)
if (body.confirmation != body.newPassword)
return ErrorHandler.badRequest("Invalid password confirmation")

session.userCredentialManager()
.updateCredential(opexRealm, user, UserCredentialModel.password(body.newPassword, false))
Expand All @@ -147,7 +147,8 @@ class UserManagementResource(private val session: KeycloakSession) : RealmResour
if (!auth.hasScopeAccess("trust")) return ErrorHandler.forbidden()

val user = session.users().getUserById(auth.getUserId(), opexRealm) ?: return ErrorHandler.userNotFound()
if (is2FAEnabled(user)) return ErrorHandler.response(Response.Status.BAD_REQUEST, OpexError.OTPAlreadyEnabled)
if (is2FAEnabled(user))
return ErrorHandler.response(Response.Status.BAD_REQUEST, OpexError.OTPAlreadyEnabled)

val secret = HmacOTP.generateSecret(64)
val uri = OTPUtils.generateOTPKeyURI(opexRealm, secret, "Opex", user.email)
Expand All @@ -164,9 +165,13 @@ class UserManagementResource(private val session: KeycloakSession) : RealmResour
if (!auth.hasScopeAccess("trust")) return ErrorHandler.forbidden()

val user = session.users().getUserById(auth.getUserId(), opexRealm) ?: return ErrorHandler.userNotFound()
if (is2FAEnabled(user)) return ErrorHandler.response(Response.Status.BAD_REQUEST, OpexError.OTPAlreadyEnabled)
if (is2FAEnabled(user))
return ErrorHandler.response(Response.Status.BAD_REQUEST, OpexError.OTPAlreadyEnabled)

val otpCredential = OTPCredentialModel.createFromPolicy(opexRealm, body.secret)
if (!CredentialValidation.validOTP(body.initialCode, otpCredential, opexRealm.otpPolicy.lookAheadWindow))
return ErrorHandler.response(Response.Status.BAD_REQUEST, OpexError.InvalidOTP)

CredentialHelper.createOTPCredential(session, opexRealm, user, body.initialCode, otpCredential)
return Response.noContent().build()
}
Expand Down Expand Up @@ -205,6 +210,37 @@ class UserManagementResource(private val session: KeycloakSession) : RealmResour
return Response.ok(UserSecurityCheckResponse(is2FAEnabled(user))).build()
}

@POST
@Path("user/logout")
@Produces(MediaType.APPLICATION_JSON)
fun logout(): Response {
val auth = ResourceAuthenticator.bearerAuth(session)
if (!auth.hasScopeAccess("trust"))
return ErrorHandler.forbidden()

val userSession = session.sessions().getUserSession(opexRealm, auth.token?.sessionState!!)
AuthenticationManager.backchannelLogout(session, userSession, true)
return Response.noContent().build()
}

@POST
@Path("user/sessions/{sessionId}/logout")
@Produces(MediaType.APPLICATION_JSON)
fun logout(@PathParam("sessionId") sessionId: String): Response {
val auth = ResourceAuthenticator.bearerAuth(session)
if (!auth.hasScopeAccess("trust"))
return ErrorHandler.forbidden()

val userSession = session.sessions().getUserSession(opexRealm, sessionId)
?: return ErrorHandler.notFound("Session not found")

if (userSession.user.id != auth.getUserId())
return ErrorHandler.forbidden()

AuthenticationManager.backchannelLogout(session, userSession, true)
return Response.noContent().build()
}

@GET
@Path("user/sessions")
@Produces(MediaType.APPLICATION_JSON)
Expand All @@ -214,7 +250,16 @@ class UserManagementResource(private val session: KeycloakSession) : RealmResour

val user = session.users().getUserById(auth.getUserId(), opexRealm) ?: return ErrorHandler.userNotFound()
val sessions = session.sessions().getUserSessionsStream(opexRealm, user)
.map { UserSessionResponse(it.ipAddress, it.started, it.lastSessionRefresh, it.state.name) }.toList()
.map {
UserSessionResponse(
it.id,
it.ipAddress,
it.started.toLong(),
it.lastSessionRefresh.toLong(),
it.state.name,
auth.token?.sessionState == it.id
)
}.toList()

return Response.ok(sessions).build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ object ErrorHandler {

fun forbidden(message: String? = null) = response(Response.Status.FORBIDDEN, OpexError.Forbidden, message)

fun badRequest(message: String? = null) = response(Response.Status.BAD_REQUEST, OpexError.BadRequest, message)

fun notFound(message: String? = null) = response(Response.Status.NOT_FOUND, OpexError.NotFound, message)

fun userNotFound(message: String? = null) = response(Response.Status.NOT_FOUND, OpexError.UserNotFound, message)

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import org.keycloak.services.managers.AuthenticationManager

class ResourceAuthenticator(private val result: AuthenticationManager.AuthResult?) {

private val user: UserModel? = result?.user
private val token: AccessToken? = result?.token
val user: UserModel? = result?.user
val token: AccessToken? = result?.token

fun getUserId() = user?.id

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum class OpexError(val code: Int, val message: String?, val status: HttpStatus
GroupNotFound(5002, "Group not found", HttpStatus.NOT_FOUND),
OTPAlreadyEnabled(5003, "2FA/OTP already configured", HttpStatus.BAD_REQUEST),
UserNotFound(5004, "User not found", HttpStatus.NOT_FOUND),
InvalidOTP(5005, "Invalid OTP", HttpStatus.BAD_REQUEST),

// code 6000: wallet
WalletOwnerNotFound(6001, null, HttpStatus.NOT_FOUND),
Expand Down