+login by token and server library improvements

This commit is contained in:
Sergey Chernov 2022-11-26 17:52:09 +01:00
parent 5df6709983
commit 449de2e504
5 changed files with 70 additions and 20 deletions

View File

@ -44,7 +44,7 @@ kotlin {
dependencies { dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3")
api("net.sergeych:unikrypto:1.2.1-SNAPSHOT") api("net.sergeych:unikrypto:1.2.1-SNAPSHOT")
api("net.sergeych:parsec3:0.3.1-SNAPSHOT") api("net.sergeych:parsec3:0.3.2-SNAPSHOT")
api("net.sergeych:boss-serialization-mp:0.2.4-SNAPSHOT") api("net.sergeych:boss-serialization-mp:0.2.4-SNAPSHOT")
3 } 3 }
} }

View File

@ -65,7 +65,7 @@ class SuperloginClient<D, S : WithAdapter>(
adapterReady = CompletableDeferred() adapterReady = CompletableDeferred()
} }
globalLaunch { globalLaunch {
transport.adapter().invokeCommand(api.slLogout) transport.adapter().invokeCommand(serverApi.slLogout)
adapterReady.complete(Unit) adapterReady.complete(Unit)
} }
} else { } else {
@ -102,12 +102,12 @@ class SuperloginClient<D, S : WithAdapter>(
private var jobs = listOf<Job>() private var jobs = listOf<Job>()
private val api = SuperloginServerApi<WithAdapter>() private val serverApi = SuperloginServerApi<WithAdapter>()
private suspend fun tryRestoreLogin() { private suspend fun tryRestoreLogin() {
slData?.loginToken?.let { token -> slData?.loginToken?.let { token ->
try { try {
val ar = transport.adapter().invokeCommand(api.slLoginByToken, token) val ar = transport.adapter().invokeCommand(serverApi.slLoginByToken, token)
slData = if (ar is AuthenticationResult.Success) { slData = if (ar is AuthenticationResult.Success) {
val data: D? = ar.applicationData?.let { BossDecoder.decodeFrom(dataType, it) } val data: D? = ar.applicationData?.let { BossDecoder.decodeFrom(dataType, it) }
SuperloginData(ar.loginToken, data) SuperloginData(ar.loginToken, data)
@ -170,11 +170,14 @@ class SuperloginClient<D, S : WithAdapter>(
return rn.registerWithData(loginName, password, extraData = BossEncoder.encode(dataType, data)) return rn.registerWithData(loginName, password, extraData = BossEncoder.encode(dataType, data))
.also { rr -> .also { rr ->
if (rr is Registration.Result.Success) { if (rr is Registration.Result.Success) {
slData = SuperloginData(rr.loginToken, rr.encodedData?.let { BossDecoder.decodeFrom(dataType, it) }) slData = SuperloginData(rr.loginToken, extractData(rr.encodedData))
} }
} }
} }
private fun extractData(rr: ByteArray?): D?
= rr?.let { BossDecoder.decodeFrom(dataType, it) }
private fun mustBeLoggedOut() { private fun mustBeLoggedOut() {
if (isLoggedIn) if (isLoggedIn)
throw IllegalStateException("please log out first") throw IllegalStateException("please log out first")
@ -190,14 +193,24 @@ class SuperloginClient<D, S : WithAdapter>(
slData = null slData = null
} }
suspend fun LoginByToken(token: ByteArray): SuperloginData<D> { /**
* Try to log in by specified token, returned by [Registration.Result.Success.loginToken] or
* [SuperloginData.loginToken] respectively.
*
* @return updated login data (and new token value) or null if token is not (or not anymore)
* available for logging in.
*/
suspend fun loginByToken(token: ByteArray): SuperloginData<D>? {
mustBeLoggedOut() mustBeLoggedOut()
val r = invoke(api.slLoginByToken,token) val r = invoke(serverApi.slLoginByToken,token)
when(r) { return when(r) {
AuthenticationResult.LoginIdUnavailable -> TODO() AuthenticationResult.LoginIdUnavailable -> TODO()
AuthenticationResult.LoginUnavailable -> TODO() AuthenticationResult.LoginUnavailable -> null
AuthenticationResult.RestoreIdUnavailable -> TODO() AuthenticationResult.RestoreIdUnavailable -> TODO()
is AuthenticationResult.Success -> TODO() is AuthenticationResult.Success -> SuperloginData(
r.loginToken,
extractData(r.applicationData)
)
} }
} }

View File

@ -23,5 +23,20 @@ interface SLServerTraits {
*/ */
suspend fun register(registrationArgs: RegistrationArgs): AuthenticationResult suspend fun register(registrationArgs: RegistrationArgs): AuthenticationResult
/**
* Logging out procedure does not need any extra logic unless reuired by application
* server software. Default implementation does nothing.
*/
suspend fun logout() {} suspend fun logout() {}
/**
* Try to log in using an authentication token, which normally is returned in
* [AuthenticationResult.Success.loginToken]. If the server implementation
* does not support login token, don't implement it, use the default implementation.
*
* Otherwise, implement login by token and return either [AuthenticationResult.Success]
* or [AuthenticationResult.LoginUnavailable]. So not return anything else.
*/
suspend fun loginByToken(token: ByteArray): AuthenticationResult
= AuthenticationResult.LoginUnavailable
} }

View File

@ -25,4 +25,7 @@ inline fun <reified D, T : SLServerSession<D>, H : CommandHost<T>> AdapterBuilde
loginName = null loginName = null
traits.logout() traits.logout()
} }
on(a2.slLoginByToken) { token ->
traits.loginByToken(token)
}
} }

View File

@ -4,16 +4,15 @@ import io.ktor.server.engine.*
import io.ktor.server.netty.* import io.ktor.server.netty.*
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.sergeych.parsec3.CommandHost
import net.sergeych.parsec3.Parsec3WSClient import net.sergeych.parsec3.Parsec3WSClient
import net.sergeych.parsec3.WithAdapter import net.sergeych.parsec3.WithAdapter
import net.sergeych.parsec3.parsec3TransportServer import net.sergeych.parsec3.parsec3TransportServer
import net.sergeych.superlogin.AuthenticationResult import net.sergeych.superlogin.AuthenticationResult
import net.sergeych.superlogin.RegistrationArgs import net.sergeych.superlogin.RegistrationArgs
import net.sergeych.superlogin.SuperloginServerApi
import net.sergeych.superlogin.client.LoginState import net.sergeych.superlogin.client.LoginState
import net.sergeych.superlogin.client.Registration import net.sergeych.superlogin.client.Registration
import net.sergeych.superlogin.client.SuperloginClient import net.sergeych.superlogin.client.SuperloginClient
import net.sergeych.superlogin.server.SLServerApiBase
import net.sergeych.superlogin.server.SLServerSession import net.sergeych.superlogin.server.SLServerSession
import net.sergeych.superlogin.server.SLServerTraits import net.sergeych.superlogin.server.SLServerTraits
import net.sergeych.superlogin.server.superloginServer import net.sergeych.superlogin.server.superloginServer
@ -22,11 +21,12 @@ import kotlin.random.Random
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertIs import kotlin.test.assertIs
import kotlin.test.assertNotNull
data class TestSession(var buzz: String = "BuZZ") : SLServerSession<TestData>() data class TestSession(var buzz: String = "BuZZ") : SLServerSession<TestData>()
object TestApiServer : SLServerApiBase<TestData>() { class TestApiServer<T : WithAdapter> : CommandHost<T>() {
val loginName by command<Unit, String?>() val loginName by command<Unit, String?>()
} }
@ -56,6 +56,12 @@ object TestServerTraits : SLServerTraits {
} }
} }
override suspend fun loginByToken(token: ByteArray): AuthenticationResult {
return byToken[token.toList()]?.let {
AuthenticationResult.Success(token, it.extraData)
} ?: AuthenticationResult.LoginUnavailable
}
} }
@Serializable @Serializable
@ -63,6 +69,16 @@ data class TestData(
val foo: String, val foo: String,
) )
//fun <S: SLServerSession<*>,A: CommandHost<S>> Application.superloginServer(
// traits: SLServerTraits,
// api: A,
// f: AdapterBuilder<S,A>.()->Unit) {
// parsec3TransportServer(api) {
// superloginServer(traits)
// f()
// }
//}
internal class WsServerKtTest { internal class WsServerKtTest {
@ -71,7 +87,8 @@ internal class WsServerKtTest {
fun testWsServer() { fun testWsServer() {
embeddedServer(Netty, port = 8080) { embeddedServer(Netty, port = 8080) {
parsec3TransportServer(TestApiServer) { parsec3TransportServer(TestApiServer<SLServerSession<TestData>>()) {
// superloginServer(TestServerTraits,TestApiServer<SLServerSession<TestData>>()) {
newSession { TestSession() } newSession { TestSession() }
superloginServer(TestServerTraits) superloginServer(TestServerTraits)
on(api.loginName) { on(api.loginName) {
@ -80,11 +97,10 @@ internal class WsServerKtTest {
} }
}.start(wait = false) }.start(wait = false)
val client = Parsec3WSClient("ws://localhost:8080/api/p3", SuperloginServerApi<WithAdapter>()) { val client = Parsec3WSClient("ws://localhost:8080/api/p3")
}
runBlocking { runBlocking {
val api = TestApiServer<WithAdapter>()
val slc = SuperloginClient<TestData, WithAdapter>(client) val slc = SuperloginClient<TestData, WithAdapter>(client)
assertEquals(LoginState.LoggedOut, slc.state.value) assertEquals(LoginState.LoggedOut, slc.state.value)
var rt = slc.register("foo", "passwd", TestData("bar!")) var rt = slc.register("foo", "passwd", TestData("bar!"))
@ -94,7 +110,7 @@ internal class WsServerKtTest {
println(rt.secret) println(rt.secret)
assertEquals("bar!", rt.data<TestData>()?.foo) assertEquals("bar!", rt.data<TestData>()?.foo)
assertEquals("foo", slc.call(TestApiServer.loginName)) assertEquals("foo", slc.call(api.loginName))
val s = slc.state.value val s = slc.state.value
assertIs<LoginState.LoggedIn<TestData>>(s) assertIs<LoginState.LoggedIn<TestData>>(s)
@ -105,12 +121,15 @@ internal class WsServerKtTest {
} }
slc.logout() slc.logout()
assertIs<LoginState.LoggedOut>(slc.state.value) assertIs<LoginState.LoggedOut>(slc.state.value)
assertEquals(null, slc.call(TestApiServer.loginName)) assertEquals(null, slc.call(api.loginName))
rt = slc.register("foo", "passwd", TestData("nobar")) rt = slc.register("foo", "passwd", TestData("nobar"))
assertIs<Registration.Result.InvalidLogin>(rt) assertIs<Registration.Result.InvalidLogin>(rt)
// slc.loginByToken() var ar = slc.loginByToken(token)
assertNotNull(ar)
assertEquals("bar!", ar.data?.foo)
assertEquals("foo", slc.call(api.loginName))
} }
} }