diff --git a/build.gradle.kts b/build.gradle.kts index 18986f0..fcfec9b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -44,7 +44,7 @@ kotlin { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3") 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") 3 } } diff --git a/src/commonMain/kotlin/net.sergeych.superlogin/client/SuperloginClient.kt b/src/commonMain/kotlin/net.sergeych.superlogin/client/SuperloginClient.kt index 43537be..5669c09 100644 --- a/src/commonMain/kotlin/net.sergeych.superlogin/client/SuperloginClient.kt +++ b/src/commonMain/kotlin/net.sergeych.superlogin/client/SuperloginClient.kt @@ -65,7 +65,7 @@ class SuperloginClient( adapterReady = CompletableDeferred() } globalLaunch { - transport.adapter().invokeCommand(api.slLogout) + transport.adapter().invokeCommand(serverApi.slLogout) adapterReady.complete(Unit) } } else { @@ -102,12 +102,12 @@ class SuperloginClient( private var jobs = listOf() - private val api = SuperloginServerApi() + private val serverApi = SuperloginServerApi() private suspend fun tryRestoreLogin() { slData?.loginToken?.let { token -> try { - val ar = transport.adapter().invokeCommand(api.slLoginByToken, token) + val ar = transport.adapter().invokeCommand(serverApi.slLoginByToken, token) slData = if (ar is AuthenticationResult.Success) { val data: D? = ar.applicationData?.let { BossDecoder.decodeFrom(dataType, it) } SuperloginData(ar.loginToken, data) @@ -170,11 +170,14 @@ class SuperloginClient( return rn.registerWithData(loginName, password, extraData = BossEncoder.encode(dataType, data)) .also { rr -> 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() { if (isLoggedIn) throw IllegalStateException("please log out first") @@ -190,14 +193,24 @@ class SuperloginClient( slData = null } - suspend fun LoginByToken(token: ByteArray): SuperloginData { + /** + * 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? { mustBeLoggedOut() - val r = invoke(api.slLoginByToken,token) - when(r) { + val r = invoke(serverApi.slLoginByToken,token) + return when(r) { AuthenticationResult.LoginIdUnavailable -> TODO() - AuthenticationResult.LoginUnavailable -> TODO() + AuthenticationResult.LoginUnavailable -> null AuthenticationResult.RestoreIdUnavailable -> TODO() - is AuthenticationResult.Success -> TODO() + is AuthenticationResult.Success -> SuperloginData( + r.loginToken, + extractData(r.applicationData) + ) } } diff --git a/src/commonMain/kotlin/net.sergeych.superlogin/server/SLServerTraits.kt b/src/commonMain/kotlin/net.sergeych.superlogin/server/SLServerTraits.kt index 8562aa4..88e6d01 100644 --- a/src/commonMain/kotlin/net.sergeych.superlogin/server/SLServerTraits.kt +++ b/src/commonMain/kotlin/net.sergeych.superlogin/server/SLServerTraits.kt @@ -23,5 +23,20 @@ interface SLServerTraits { */ 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() {} + + /** + * 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 } \ No newline at end of file diff --git a/src/jvmMain/kotlin/net/sergeych/superlogin/server/SuperloginServer.kt b/src/jvmMain/kotlin/net/sergeych/superlogin/server/SuperloginServer.kt index b1eda31..cf81768 100644 --- a/src/jvmMain/kotlin/net/sergeych/superlogin/server/SuperloginServer.kt +++ b/src/jvmMain/kotlin/net/sergeych/superlogin/server/SuperloginServer.kt @@ -25,4 +25,7 @@ inline fun , H : CommandHost> AdapterBuilde loginName = null traits.logout() } + on(a2.slLoginByToken) { token -> + traits.loginByToken(token) + } } \ No newline at end of file diff --git a/src/jvmTest/kotlin/net/sergeych/WsServerKtTest.kt b/src/jvmTest/kotlin/net/sergeych/WsServerKtTest.kt index bcf912d..70c34b3 100644 --- a/src/jvmTest/kotlin/net/sergeych/WsServerKtTest.kt +++ b/src/jvmTest/kotlin/net/sergeych/WsServerKtTest.kt @@ -4,16 +4,15 @@ import io.ktor.server.engine.* import io.ktor.server.netty.* import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable +import net.sergeych.parsec3.CommandHost import net.sergeych.parsec3.Parsec3WSClient import net.sergeych.parsec3.WithAdapter import net.sergeych.parsec3.parsec3TransportServer import net.sergeych.superlogin.AuthenticationResult import net.sergeych.superlogin.RegistrationArgs -import net.sergeych.superlogin.SuperloginServerApi import net.sergeych.superlogin.client.LoginState import net.sergeych.superlogin.client.Registration import net.sergeych.superlogin.client.SuperloginClient -import net.sergeych.superlogin.server.SLServerApiBase import net.sergeych.superlogin.server.SLServerSession import net.sergeych.superlogin.server.SLServerTraits import net.sergeych.superlogin.server.superloginServer @@ -22,11 +21,12 @@ import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs +import kotlin.test.assertNotNull data class TestSession(var buzz: String = "BuZZ") : SLServerSession() -object TestApiServer : SLServerApiBase() { +class TestApiServer : CommandHost() { val loginName by command() } @@ -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 @@ -63,6 +69,16 @@ data class TestData( val foo: String, ) +//fun ,A: CommandHost> Application.superloginServer( +// traits: SLServerTraits, +// api: A, +// f: AdapterBuilder.()->Unit) { +// parsec3TransportServer(api) { +// superloginServer(traits) +// f() +// } +//} + internal class WsServerKtTest { @@ -71,7 +87,8 @@ internal class WsServerKtTest { fun testWsServer() { embeddedServer(Netty, port = 8080) { - parsec3TransportServer(TestApiServer) { + parsec3TransportServer(TestApiServer>()) { +// superloginServer(TestServerTraits,TestApiServer>()) { newSession { TestSession() } superloginServer(TestServerTraits) on(api.loginName) { @@ -80,11 +97,10 @@ internal class WsServerKtTest { } }.start(wait = false) - val client = Parsec3WSClient("ws://localhost:8080/api/p3", SuperloginServerApi()) { - } - + val client = Parsec3WSClient("ws://localhost:8080/api/p3") runBlocking { + val api = TestApiServer() val slc = SuperloginClient(client) assertEquals(LoginState.LoggedOut, slc.state.value) var rt = slc.register("foo", "passwd", TestData("bar!")) @@ -94,7 +110,7 @@ internal class WsServerKtTest { println(rt.secret) assertEquals("bar!", rt.data()?.foo) - assertEquals("foo", slc.call(TestApiServer.loginName)) + assertEquals("foo", slc.call(api.loginName)) val s = slc.state.value assertIs>(s) @@ -105,12 +121,15 @@ internal class WsServerKtTest { } slc.logout() assertIs(slc.state.value) - assertEquals(null, slc.call(TestApiServer.loginName)) + assertEquals(null, slc.call(api.loginName)) rt = slc.register("foo", "passwd", TestData("nobar")) assertIs(rt) -// slc.loginByToken() + var ar = slc.loginByToken(token) + assertNotNull(ar) + assertEquals("bar!", ar.data?.foo) + assertEquals("foo", slc.call(api.loginName)) } }