started 0.1.*:
- keep an retrieve data key by password - renamed resetPasswordAndSignIn to avoid confusion
This commit is contained in:
parent
f5cd7a3819
commit
71a5f3c8f1
@ -9,7 +9,7 @@ val logback_version="1.2.10"
|
||||
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.0.2-SNAPSHOT"
|
||||
version = "0.1.0-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
@ -16,6 +16,7 @@ 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 kotlin.reflect.KType
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
@ -59,6 +60,7 @@ class SuperloginClient<D, S : WithAdapter>(
|
||||
// do actual disconnect work
|
||||
_cflow.value = false
|
||||
_state.value = LoginState.LoggedOut
|
||||
dataKey = null
|
||||
} else {
|
||||
val v = _state.value
|
||||
if (v !is LoginState.LoggedIn<*> || v.loginData != value) {
|
||||
@ -96,6 +98,7 @@ class SuperloginClient<D, S : WithAdapter>(
|
||||
* Call client API commands with it (uses [adapter] under the hood)
|
||||
*/
|
||||
suspend fun <A, R> call(ca: CommandDescriptor<A, R>, args: A): R = adapter().invokeCommand(ca, args)
|
||||
|
||||
/**
|
||||
* Call client API commands with it (uses [adapter] under the hood)
|
||||
*/
|
||||
@ -113,7 +116,7 @@ class SuperloginClient<D, S : WithAdapter>(
|
||||
private suspend fun tryRestoreLogin() {
|
||||
slData?.loginToken?.let { token ->
|
||||
debug { "trying to restore login with a token" }
|
||||
while( true ) {
|
||||
while (true) {
|
||||
try {
|
||||
val ar = transport.adapter().invokeCommand(serverApi.slLoginByToken, token)
|
||||
slData = if (ar is AuthenticationResult.Success) {
|
||||
@ -172,6 +175,15 @@ class SuperloginClient<D, S : WithAdapter>(
|
||||
*/
|
||||
val isLoggedIn get() = state.value.isLoggedIn
|
||||
|
||||
/**
|
||||
* The data storage key, a random key created when registering. It is retrieved automatically
|
||||
* on login by password and on registration, _It does not restore when logged in by key
|
||||
* as it would require storing user's password which must not be done_.
|
||||
* Use [retrieveDataKey] to restore to with a password (when logged in)
|
||||
*/
|
||||
var dataKey: SymmetricKey? = null
|
||||
private set
|
||||
|
||||
/**
|
||||
* Perform registration and login attempt and return the result. It automatically caches and reuses intermediate
|
||||
* long-calculated keys so it runs much fatser when called again with the same password.
|
||||
@ -193,6 +205,7 @@ class SuperloginClient<D, S : WithAdapter>(
|
||||
.also { rr ->
|
||||
if (rr is Registration.Result.Success) {
|
||||
slData = SuperloginData(loginName, rr.loginToken, extractData(rr.encodedData))
|
||||
dataKey = rr.dataKey
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -264,7 +277,10 @@ class SuperloginClient<D, S : WithAdapter>(
|
||||
)
|
||||
if (result is AuthenticationResult.Success) {
|
||||
SuperloginData(loginName, result.loginToken, extractData(result.applicationData))
|
||||
.also { slData = it }
|
||||
.also {
|
||||
slData = it
|
||||
dataKey = aco.data.payload.dataStorageKey
|
||||
}
|
||||
} else null
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
@ -274,6 +290,42 @@ class SuperloginClient<D, S : WithAdapter>(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to retrieve dataKey (usually after login by token), it is impossible
|
||||
* to do without a valid password key. Should be logged in. If [dataKey] is not null
|
||||
* what means is already known, returns it immediatel, otherwise uses password
|
||||
* to access ACO on the server and extract data key.
|
||||
* @return data key or null if it is impossible to do (no connection or wrong password)
|
||||
*/
|
||||
suspend fun retrieveDataKey(password: String): SymmetricKey? {
|
||||
mustBeLoggedIn()
|
||||
dataKey?.let { return it }
|
||||
|
||||
val loginName = slData?.loginName ?: throw SLInternalException("slData: empty login name")
|
||||
try {
|
||||
val params = invoke(
|
||||
serverApi.slRequestDerivationParams,
|
||||
loginName
|
||||
)
|
||||
val keys = DerivedKeys.derive(password, params)
|
||||
// Request login data by derived it
|
||||
return invoke(
|
||||
serverApi.slRequestACOByLoginName,
|
||||
RequestACOByLoginNameArgs(loginName, keys.loginId)
|
||||
).let { loginRequest ->
|
||||
AccessControlObject.unpackWithKey<SuperloginRestoreAccessPayload>(
|
||||
loginRequest.packedACO,
|
||||
keys.loginAccessKey
|
||||
)?.let { aco ->
|
||||
aco.data.payload.dataStorageKey.also { dataKey = it }
|
||||
}
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets password and log in using a `secret` string (one that wwas reported on registration. __Never store
|
||||
* secrt string in your app__. Always ask user to enter it just before the operation and wipe it out
|
||||
@ -286,10 +338,10 @@ class SuperloginClient<D, S : WithAdapter>(
|
||||
* @param params password derivation params: it is possible to change its strength here
|
||||
* @return login data instance on success or null
|
||||
*/
|
||||
suspend fun resetPasswordAndLogin(
|
||||
suspend fun resetPasswordAndSignIn(
|
||||
secret: String, newPassword: String,
|
||||
params: PasswordDerivationParams = PasswordDerivationParams(),
|
||||
loginKeyStrength: Int = 2048
|
||||
loginKeyStrength: Int = 2048,
|
||||
): SuperloginData<D>? {
|
||||
mustBeLoggedOut()
|
||||
return try {
|
||||
@ -297,6 +349,7 @@ class SuperloginClient<D, S : WithAdapter>(
|
||||
val packedACO = invoke(serverApi.slRequestACOBySecretId, id)
|
||||
AccessControlObject.unpackWithKey<SuperloginRestoreAccessPayload>(packedACO, key)?.let {
|
||||
changePasswordWithACO(it, newPassword)
|
||||
dataKey = it.data.payload.dataStorageKey
|
||||
slData
|
||||
}
|
||||
} catch (x: RestoreKey.InvalidSecretException) {
|
||||
@ -338,9 +391,11 @@ class SuperloginClient<D, S : WithAdapter>(
|
||||
val result = invoke(
|
||||
serverApi.slChangePasswordAndLogin, ChangePasswordArgs(
|
||||
aco.payload.login,
|
||||
SignedRecord.pack(aco.payload.loginPrivateKey,
|
||||
ChangePasswordPayload(newAco.packed,params,newLoginKey.await().publicKey,keys.loginId),
|
||||
deferredNonce.await())
|
||||
SignedRecord.pack(
|
||||
aco.payload.loginPrivateKey,
|
||||
ChangePasswordPayload(newAco.packed, params, newLoginKey.await().publicKey, keys.loginId),
|
||||
deferredNonce.await()
|
||||
)
|
||||
)
|
||||
)
|
||||
when (result) {
|
||||
@ -367,23 +422,25 @@ class SuperloginClient<D, S : WithAdapter>(
|
||||
* @param loginKeyStrength login key is regenerateed so its strength could be updated here
|
||||
* @return true if the password has been successfully changed
|
||||
*/
|
||||
suspend fun changePassword(oldPassword: String, newPassword: String,
|
||||
passwordDerivationParams: PasswordDerivationParams = PasswordDerivationParams(),
|
||||
loginKeyStrength: Int = 2048
|
||||
suspend fun changePassword(
|
||||
oldPassword: String, newPassword: String,
|
||||
passwordDerivationParams: PasswordDerivationParams = PasswordDerivationParams(),
|
||||
loginKeyStrength: Int = 2048,
|
||||
): Boolean {
|
||||
mustBeLoggedIn()
|
||||
val loginName = slData?.loginName ?: throw SLInternalException("loginName should be defined here")
|
||||
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)?.let {
|
||||
changePasswordWithACO(it, newPassword,passwordDerivationParams, loginKeyStrength)
|
||||
} ?: false
|
||||
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)
|
||||
?.let {
|
||||
changePasswordWithACO(it, newPassword, passwordDerivationParams, loginKeyStrength)
|
||||
} ?: false
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
inline operator fun <reified D,S : WithAdapter> invoke(
|
||||
inline operator fun <reified D, S : WithAdapter> invoke(
|
||||
t: Parsec3Transport<S>,
|
||||
savedData: SuperloginData<D>? = null,
|
||||
): SuperloginClient<D, S> {
|
||||
|
@ -136,9 +136,12 @@ internal class WsServerKtTest {
|
||||
runBlocking {
|
||||
val api = TestApiServer<WithAdapter>()
|
||||
val slc = SuperloginClient<TestData, WithAdapter>(client)
|
||||
assertNull(slc.dataKey)
|
||||
assertEquals(LoginState.LoggedOut, slc.state.value)
|
||||
var rt = slc.register("foo", "passwd", TestData("bar!"))
|
||||
val dk1 = slc.dataKey
|
||||
assertIs<Registration.Result.Success>(rt)
|
||||
assertEquals(dk1, rt.dataKey)
|
||||
val secret = rt.secret
|
||||
var token = rt.loginToken
|
||||
println(rt.secret)
|
||||
@ -154,6 +157,7 @@ internal class WsServerKtTest {
|
||||
slc.register("foo", "passwd", TestData("nobar"))
|
||||
}
|
||||
slc.logout()
|
||||
assertNull(slc.dataKey)
|
||||
assertIs<LoginState.LoggedOut>(slc.state.value)
|
||||
assertEquals(null, slc.call(api.loginName))
|
||||
|
||||
@ -164,9 +168,14 @@ internal class WsServerKtTest {
|
||||
|
||||
var ar = slc.loginByToken(token)
|
||||
assertNotNull(ar)
|
||||
assertNull(slc.dataKey)
|
||||
assertEquals("bar!", ar.data?.foo)
|
||||
assertTrue { slc.isLoggedIn }
|
||||
assertEquals("foo", slc.call(api.loginName))
|
||||
|
||||
assertNull(slc.retrieveDataKey("badpasswd"))
|
||||
assertEquals(dk1?.id, slc.retrieveDataKey("passwd")?.id)
|
||||
assertEquals(dk1?.id, slc.dataKey?.id)
|
||||
//
|
||||
assertThrowsAsync<IllegalStateException> { slc.loginByToken(token) }
|
||||
}
|
||||
@ -187,6 +196,7 @@ internal class WsServerKtTest {
|
||||
assertEquals(LoginState.LoggedOut, slc.state.value)
|
||||
var rt = slc.register("foo", "passwd", TestData("bar!"))
|
||||
assertIs<Registration.Result.Success>(rt)
|
||||
val dk1 = slc.dataKey!!
|
||||
slc.logout()
|
||||
assertNull(slc.loginByPassword("foo", "passwd2"))
|
||||
var ar = slc.loginByPassword("foo", "passwd")
|
||||
@ -194,6 +204,7 @@ internal class WsServerKtTest {
|
||||
assertEquals("bar!", ar.data?.foo)
|
||||
assertTrue { slc.isLoggedIn }
|
||||
assertEquals("foo", slc.call(api.loginName))
|
||||
assertEquals(dk1.id, slc.dataKey!!.id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,9 +237,9 @@ internal class WsServerKtTest {
|
||||
assertEquals("foo", slc.call(api.loginName))
|
||||
|
||||
slc.logout()
|
||||
assertNull(slc.resetPasswordAndLogin("bad_secret", "newpass2"))
|
||||
assertNull(slc.resetPasswordAndLogin("3PBpp-Aris5-ogdV7-Abz36-ggGH5", "newpass2"))
|
||||
ar = slc.resetPasswordAndLogin(secret,"newpass2")
|
||||
assertNull(slc.resetPasswordAndSignIn("bad_secret", "newpass2"))
|
||||
assertNull(slc.resetPasswordAndSignIn("3PBpp-Aris5-ogdV7-Abz36-ggGH5", "newpass2"))
|
||||
ar = slc.resetPasswordAndSignIn(secret,"newpass2")
|
||||
assertNotNull(ar)
|
||||
assertEquals("bar!", ar.data?.foo)
|
||||
assertTrue { slc.isLoggedIn }
|
||||
|
Loading…
Reference in New Issue
Block a user