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