fix(auth): a setup's password replaced ends the account's other sessions
The full reading's L4: password.set replacing the password of a setup never finished (no authenticator confirmed yet) ended no session, against the rule that every new password ends the account's other sessions. Now it ends them all but the session asking, here and on every member (as a change does; keepSession, which a member forwards). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
003cb4821a
commit
8d5d42cc8e
8 changed files with 53 additions and 3 deletions
|
|
@ -50,6 +50,11 @@ class PasswordEndpoint extends Endpoint {
|
|||
where: (t) => t.authUserId.equals(person),
|
||||
) >
|
||||
0,
|
||||
keepSession: switch (session.authenticated!.authId) {
|
||||
final id when Uuid.isValidUUID(fromString: id) =>
|
||||
UuidValue.withValidation(id),
|
||||
_ => null,
|
||||
},
|
||||
);
|
||||
if (isMember(session.serverpod)) {
|
||||
await becomeIdentified(session, person);
|
||||
|
|
|
|||
|
|
@ -374,6 +374,7 @@ Future<void> setPassword(
|
|||
String? code,
|
||||
bool freshGitHubSignIn = false,
|
||||
bool anonymousHere = false,
|
||||
UuidValue? keepSession,
|
||||
}) async {
|
||||
requirePasswordKeys(session.serverpod);
|
||||
if (!isPasswordSetup(setup)) {
|
||||
|
|
@ -425,7 +426,7 @@ Future<void> setPassword(
|
|||
: null;
|
||||
}
|
||||
try {
|
||||
await session.db.transaction((transaction) async {
|
||||
final replaced = await session.db.transaction((transaction) async {
|
||||
// SELECT * FROM "email_identity"
|
||||
// WHERE "authUserId" = $1 OR "addressHash" = $2
|
||||
final identities = await EmailIdentity.db.find(
|
||||
|
|
@ -495,7 +496,7 @@ Future<void> setPassword(
|
|||
passwordRowOf(person, setup, now: now),
|
||||
transaction: transaction,
|
||||
);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (await hadConfirmedAuthenticator(
|
||||
session,
|
||||
|
|
@ -510,7 +511,20 @@ Future<void> setPassword(
|
|||
passwordRowOf(person, setup, now: now, row: existing),
|
||||
transaction: transaction,
|
||||
);
|
||||
// A new password ends the account's other sessions, as every other
|
||||
// does (the full reading's L4): one signed in with the old would go
|
||||
// on, its keys too.
|
||||
await endSessionsEverywhere(
|
||||
session,
|
||||
person,
|
||||
keepSession: keepSession,
|
||||
transaction: transaction,
|
||||
);
|
||||
return true;
|
||||
});
|
||||
if (replaced) {
|
||||
await announceSessionsEnded(session, person);
|
||||
}
|
||||
} on DatabaseQueryException catch (error) {
|
||||
// The same person set one at the same moment: the unique index refused
|
||||
// the second (unique_violation).
|
||||
|
|
|
|||
|
|
@ -158,7 +158,8 @@ abstract interface class AccountRegister {
|
|||
/// signs in with GitHub alone a GitHub sign-in of theirs made recently
|
||||
/// ([freshGitHubSignIn], of the session asking); someone with neither
|
||||
/// only while anonymous where the session lives ([anonymousHere]).
|
||||
/// Refusals as `setPassword`'s.
|
||||
/// Replacing a setup never finished ends every other session of the
|
||||
/// account but [keepSession]. Refusals as `setPassword`'s.
|
||||
Future<void> setPassword(
|
||||
Session session, {
|
||||
required UuidValue person,
|
||||
|
|
@ -167,6 +168,7 @@ abstract interface class AccountRegister {
|
|||
String? code,
|
||||
bool freshGitHubSignIn = false,
|
||||
bool anonymousHere = false,
|
||||
UuidValue? keepSession,
|
||||
});
|
||||
|
||||
/// A password sign-in's first step for [address], the sign-ins begun from
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ Future<Map<String, Object?>?> _answer(
|
|||
// person of a member's.
|
||||
freshGitHubSignIn: arguments.flag('freshGitHubSignIn'),
|
||||
anonymousHere: arguments.flag('anonymousHere'),
|
||||
keepSession: arguments.optionalId('keepSession'),
|
||||
);
|
||||
return const {};
|
||||
case RegisterOperation.beginPasswordSignIn:
|
||||
|
|
|
|||
|
|
@ -429,6 +429,7 @@ final class LocalRegister implements AccountRegister {
|
|||
String? code,
|
||||
bool freshGitHubSignIn = false,
|
||||
bool anonymousHere = false,
|
||||
UuidValue? keepSession,
|
||||
}) => passwords.setPassword(
|
||||
session,
|
||||
person: person,
|
||||
|
|
@ -437,6 +438,7 @@ final class LocalRegister implements AccountRegister {
|
|||
code: code,
|
||||
freshGitHubSignIn: freshGitHubSignIn,
|
||||
anonymousHere: anonymousHere,
|
||||
keepSession: keepSession,
|
||||
);
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -309,6 +309,7 @@ final class RemoteRegister implements AccountRegister {
|
|||
String? code,
|
||||
bool freshGitHubSignIn = false,
|
||||
bool anonymousHere = false,
|
||||
UuidValue? keepSession,
|
||||
}) => _ask(RegisterOperation.setPassword, {
|
||||
'person': person.uuid,
|
||||
'setup': setup.toJson(),
|
||||
|
|
@ -316,6 +317,7 @@ final class RemoteRegister implements AccountRegister {
|
|||
'code': code,
|
||||
'freshGitHubSignIn': freshGitHubSignIn,
|
||||
'anonymousHere': anonymousHere,
|
||||
'keepSession': keepSession?.uuid,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -533,6 +533,29 @@ void main() {
|
|||
expect(await scopesOf(anna), isNot(contains(identified.name)));
|
||||
});
|
||||
|
||||
test('replaced before an authenticator is confirmed, ends every '
|
||||
"other session of the account (the full reading's L4)", () async {
|
||||
await annaSetsPassword();
|
||||
final annasLaptop = await signInNewDevice(session, anna);
|
||||
final code = await ownAddressCode();
|
||||
final other = await PasswordDevice.make(seed: 23);
|
||||
await endpoints.password.set(
|
||||
asAnna(),
|
||||
setup: other.setup(),
|
||||
request: code.request,
|
||||
code: code.code,
|
||||
);
|
||||
final sessions = {
|
||||
for (final row in await ServerSideSession.db.find(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(anna),
|
||||
))
|
||||
row.id,
|
||||
};
|
||||
expect(sessions, contains(annasPhone));
|
||||
expect(sessions, isNot(contains(annasLaptop)));
|
||||
});
|
||||
|
||||
test('replaces the password until an authenticator is confirmed, '
|
||||
'then is refused', () async {
|
||||
await annaSetsPassword();
|
||||
|
|
|
|||
|
|
@ -251,6 +251,7 @@ final class _Register implements AccountRegister {
|
|||
String? code,
|
||||
bool freshGitHubSignIn = false,
|
||||
bool anonymousHere = false,
|
||||
UuidValue? keepSession,
|
||||
}) => _answer('setPassword', () {
|
||||
add(person);
|
||||
passwords.add(person);
|
||||
|
|
|
|||
Loading…
Reference in a new issue