Compare commits

..
7 Commits
Author SHA1 Message Date
sergeych 92ad1a5cff migrate to kotlin 2.1, upgraded parsec3 2025-01-05 13:47:29 +03:00
sergeych 66e39a80b5 fixed changePassword behavior on wrong password 2024-12-26 03:06:33 +03:00
sergeych 49fae38b7d migrate to kotlin 2.0.21 and ktor 2.3.12 2024-12-25 19:31:39 +03:00
sergeych de3baedf0c readme fix 2023-09-25 14:38:04 +01:00
sergeych bbb7acad68 release version bump 2023-09-25 14:35:54 +01:00
sergeych cef5e06f0b support for extra reasons of registration failure 2023-09-25 14:14:50 +01:00
sergeych 13436ccedd minor release 2023-07-21 16:11:23 +01:00
10 changed files with 67 additions and 34 deletions

No files matched your search

+2
View File
@@ -5,3 +5,5 @@
/build/classes/kotlin/jvm/main/
/build/classes/kotlin/jvm/test/
/.idea
/.kotlin/
/.gigaide/gigaide.properties
+4 -2
View File
@@ -2,7 +2,9 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
> Current stable version 0.2.3
We have moved off github due to growing tensions in the world. Thanks for understanding.
> Current stable version 0.2.6
This project targets to provide command set of multiplatform tools to faciliate restore access with non-recoverable passwords by providing a backup `secret` string as the only way to reset the password. It is ready to use in android, web and server apps (native target is not yet supported because of the encryption layer not yet supporting it)
@@ -35,7 +37,7 @@ and add in dependencies like:
~~~
dependencies {
//...
implementation("net.sergeych:superlogin:0.2.3")
implementation("net.sergeych:superlogin:0.2.6")
}
~~~
+11 -13
View File
@@ -1,15 +1,15 @@
plugins {
kotlin("multiplatform") version "1.7.21"
kotlin("plugin.serialization") version "1.7.21"
kotlin("multiplatform") version "2.1.0"
kotlin("plugin.serialization") version "2.1.0"
`maven-publish`
}
val ktor_version="2.1.1"
val ktor_version="2.3.12"
val logback_version="1.2.10"
group = "net.sergeych"
version = "0.2.2"
version = "0.3.1"
repositories {
mavenCentral()
@@ -24,9 +24,9 @@ repositories {
kotlin {
jvm {
compilations.all {
kotlinOptions.jvmTarget = "1.8"
}
// compilations.all {
// kotlinOptions.jvmTarget = "1.8"
// }
withJava()
testRuns["test"].executionTask.configure {
useJUnitPlatform()
@@ -34,9 +34,9 @@ kotlin {
}
js(IR) {
browser {
commonWebpackConfig {
cssSupport.enabled = true
}
// commonWebpackConfig {
// cssSupport.enabled = true
// }
testTask {
useMocha {
timeout = "30000"
@@ -51,9 +51,7 @@ kotlin {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3")
api("net.sergeych:unikrypto:1.2.2-SNAPSHOT")
api("net.sergeych:parsec3:0.4.3-SNAPSHOT")
api("net.sergeych:boss-serialization-mp:0.2.4-SNAPSHOT")
api("net.sergeych:parsec3:0.5.2")
api("net.sergeych:unikrypto:1.2.5")
}
}
-2
View File
@@ -1,4 +1,2 @@
kotlin.code.style=official
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.enableDependencyPropagation=false
kotlin.js.generate.executable.default=false
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -19,14 +19,19 @@ data class RegistrationArgs(
val packedACO: ByteArray,
val extraData: ByteArray? = null
) {
@Suppress("unused")
inline fun <reified T>toSuccess(loginToken: ByteArray, extraData: T): AuthenticationResult.Success {
return AuthenticationResult.Success(
loginName, loginToken, BossEncoder.encode(extraData)
)
}
inline fun <reified T>decodeOrNull(): T? = extraData?.let { it.decodeBoss<T>() }
inline fun <reified T: Any>decodeOrThrow(): T = extraData?.let { it.decodeBoss<T>() }
@Suppress("unused")
inline fun <reified T>decodeOrNull(): T? = extraData?.decodeBoss<T>()
@Suppress("unused")
inline fun <reified T: Any>decodeOrThrow(): T = extraData?.decodeBoss<T>()
?: throw IllegalArgumentException("missing require extra data of type ${T::class.simpleName}")
}
@@ -44,6 +49,11 @@ sealed class AuthenticationResult {
@SerialName("LoginUnavailable")
object LoginUnavailable: AuthenticationResult()
@Serializable
@SerialName("OtherError")
class OtherError(val reason: String,val packedData: ByteArray?=null): AuthenticationResult()
@Serializable
@SerialName("LoginIdUnavailable")
object LoginIdUnavailable: AuthenticationResult()
@@ -34,6 +34,7 @@ class Registration(
val pbkdfRounds: Int = 15000,
) : LogTag("SLREG") {
@Suppress("unused")
sealed class Result {
/**
* Login is already in use or is somehow else invalid
@@ -53,6 +54,8 @@ class Registration(
val encodedData: ByteArray?) : Result() {
inline fun <reified D>data() = encodedData?.let { BossDecoder.decodeFrom<D>(it)}
}
class OtherError(val code: String,val packedData: ByteArray?=null): Result()
}
private var lastPasswordHash: ByteArray? = null
@@ -90,7 +93,7 @@ class Registration(
val nonce = adapter.invokeCommand(api.slGetNonce)
val loginPrivateKey = deferredLoginKey.await()
val spl = SuperloginRestoreAccessPayload(login, loginPrivateKey, dataKey)
repeat(10) {
repeat(3) {
val (restoreKey, restoreData) = AccessControlObject.pack(passwordKeys!!.loginAccessKey, spl)
try {
val packedArgs = SignedRecord.pack(
@@ -122,6 +125,8 @@ class Registration(
AuthenticationResult.LoginUnavailable -> return Result.InvalidLogin
is AuthenticationResult.OtherError -> return Result.OtherError(result.reason, result.packedData)
is AuthenticationResult.Success -> {
return Result.Success(
restoreKey.secret,
@@ -15,8 +15,7 @@ import net.sergeych.mp_tools.globalLaunch
import net.sergeych.parsec3.*
import net.sergeych.superlogin.*
import net.sergeych.superlogin.server.SuperloginRestoreAccessPayload
import net.sergeych.unikrypto.SignedRecord
import net.sergeych.unikrypto.SymmetricKey
import net.sergeych.unikrypto.*
import kotlin.reflect.KType
import kotlin.reflect.typeOf
@@ -394,7 +393,7 @@ class SuperloginClient<D, S : WithAdapter>(
aco.payload.dataStorageKey
)
// new ACO with a new password key and payload (but the same secret!)
var newAco = aco.updatePasswordKey(keys.loginAccessKey).updatePayload(newSlp)
val newAco = aco.updatePasswordKey(keys.loginAccessKey).updatePayload(newSlp)
// trying to update
val result = invoke(
serverApi.slChangePasswordAndLogin, ChangePasswordArgs(
@@ -422,14 +421,18 @@ class SuperloginClient<D, S : WithAdapter>(
}
/**
* Change password for a logged-in user using its known password. It is a long operation
* Change password for a logged-in user using its known password. It is a long operation.
*
* @param oldPassword existing password (re-request it from a user!)
* @param newPassword new password. we do not chek it but it should be strong - check it on your end
* @param newPassword new password. we do not check it, but it should be strong - check it on your end
* for example with [net.sergeych.unikrypto.Passwords] tools
* @param passwordDerivationParams at this point derivation parameters are alwaus updated so it is possible
* @param passwordDerivationParams at this point derivation parameters are always updated so it is possible
* to set it to desired
* @param loginKeyStrength login key is regenerateed so its strength could be updated here
* @return true if the password has been successfully changed
* @param loginKeyStrength login key is regenerated so its strength could be updated here
*
* @return true if the password has been successfully changed, false if the server didn't allow it.
*
* @throws InvalidPasswordError if the oldPassword is wrong
*/
suspend fun changePassword(
oldPassword: String, newPassword: String,
@@ -441,10 +444,24 @@ class SuperloginClient<D, S : WithAdapter>(
val dp = invoke(serverApi.slRequestDerivationParams, loginName)
val keys = DerivedKeys.derive(oldPassword, dp)
val data = invoke(serverApi.slRequestACOByLoginName, RequestACOByLoginNameArgs(loginName, keys.loginId))
return AccessControlObject.unpackWithKey<SuperloginRestoreAccessPayload>(data.packedACO, keys.loginAccessKey)
try {
return AccessControlObject.unpackWithKey<SuperloginRestoreAccessPayload>(
data.packedACO,
keys.loginAccessKey
)
?.let {
changePasswordWithACO(it, newPassword, passwordDerivationParams, loginKeyStrength)
} ?: false
} catch (e: Exception) {
when (e) {
is Container.StructureError,
is Container.DecryptionError,
is EncryptedBinaryStorage.DecryptionFailed ->
throw InvalidPasswordError()
else -> throw e
}
}
}
@@ -18,7 +18,7 @@ internal class AccessControlObjectTest {
val pk1 = SymmetricKeys.random()
val pk2 = SymmetricKeys.random()
val (rk, packed1) = AccessControlObject.pack(pk1, 117)
println(rk.secret)
// println(rk.secret)
val ac1 = AccessControlObject.unpackWithKey<Int>(packed1,pk1)
assertNotNull(ac1)
assertEquals(117, ac1.payload)
@@ -59,7 +59,7 @@ data class TestSession(val s: TestStorage) : SLServerSession<TestData>() {
}
override suspend fun loginByToken(token: ByteArray): AuthenticationResult {
println("requested login by tokeb ${token.encodeToBase64Compact()}")
println("requested login by token ${token.encodeToBase64Compact()}")
println(" ${s.byToken[token.toList()]}")
println(" ${s.byToken.size} / ${s.byLoginId.size}")
@@ -218,14 +218,15 @@ internal class WsServerKtTest {
val api = TestApiServer<WithAdapter>()
val slc = SuperloginClient<TestData, S1>(client)
assertEquals(LoginState.LoggedOut, slc.state.value)
var rt = slc.register("foo", "passwd", TestData("bar!"))
var rt = slc.register("foo", "passwd", TestData("bar!"), 2048, 140)
val dk1 = slc.dataKey!!
assertIs<Registration.Result.Success>(rt)
val secret = rt.secret
var token = rt.loginToken
assertFalse(slc.changePassword("wrong", "new"))
assertTrue(slc.changePassword("passwd", "newpass1"))
assertTrue(slc.changePassword("passwd", "newpass1",
PasswordDerivationParams(300), 2048))
assertTrue { slc.isLoggedIn }
assertEquals("foo", slc.call(api.loginName))