mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 19:07:17 +01:00
Accept an empty password when parsing a stored Firebase account
Both the Google one-tap path and the seeded-session path deliberately write `FirebaseAccount(email: ..., password: '')` -- on those devices the refresh token in the keystore is the credential, and there is no password to store. `tryParse` rejected exactly that blob. So a device signed in with Google wrote an account marker it could never read back: every later launch saw "not configured", fell through to the mirror, and left a perfectly good refresh token unused in the keystore beside it. Only widens what parses, so no caller that previously got an account back can regress. Every call site already guards `password.isEmpty` and passes null for it -- meaning "no password on this device" -- which is precisely the case this now lets them see. An empty *email* stays rejected: it carries no identity at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CFSGeWxHPY5rHUGrPqhcmG
This commit is contained in:
parent
c89fee20ec
commit
feaaf69103
@ -80,7 +80,17 @@ class FirebaseAccount {
|
||||
final email = decoded['email'];
|
||||
final password = decoded['password'];
|
||||
if (email is! String || password is! String) return null;
|
||||
if (email.isEmpty || password.isEmpty) return null;
|
||||
// An empty password is legal, and rejecting it was a real bug: both the
|
||||
// Google one-tap path and the seeded-session path deliberately store
|
||||
// `password: ''` -- the refresh token, not an address or a password, is
|
||||
// the credential on those devices. Refusing to parse the blob they had
|
||||
// just written made every such device read back as "not configured" on
|
||||
// the next launch, so a signed-in phone fell through to the mirror
|
||||
// forever. Callers already handle it: each guards `password.isEmpty` and
|
||||
// passes null, meaning "no password on this device".
|
||||
//
|
||||
// An empty *email* stays rejected: it carries no identity at all.
|
||||
if (email.isEmpty) return null;
|
||||
return FirebaseAccount(email: email, password: password);
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ name: crdt_sync
|
||||
description: >-
|
||||
Shared CRDT merge scheme + GitHub-Contents-API sync transport for
|
||||
cross-device apps. Pure Dart, no Flutter dependency.
|
||||
version: 0.10.0
|
||||
version: 0.11.0
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
|
||||
@ -67,11 +67,25 @@ void main() {
|
||||
('a missing password', '{"email":"a@b.c"}'),
|
||||
('a non-string email', '{"email":1,"password":"p"}'),
|
||||
('an empty email', '{"email":"","password":"p"}'),
|
||||
('an empty password', '{"email":"a@b.c","password":""}'),
|
||||
]) {
|
||||
test('treats $name as not configured', () {
|
||||
expect(FirebaseAccount.tryParse(raw), isNull);
|
||||
});
|
||||
}
|
||||
|
||||
test('accepts an empty password, which Google sign-in writes', () {
|
||||
// Regression: this used to return null, so a device that signed in with
|
||||
// Google -- or adopted a seeded session -- wrote an account marker it
|
||||
// could never read back. Every later launch reported "not configured"
|
||||
// and fell through to the mirror, while a perfectly good refresh token
|
||||
// sat unused in the keystore beside it.
|
||||
final parsed = FirebaseAccount.tryParse(
|
||||
const FirebaseAccount(email: 'a@b.c', password: '').toJsonString(),
|
||||
);
|
||||
|
||||
expect(parsed, isNotNull);
|
||||
expect(parsed!.email, 'a@b.c');
|
||||
expect(parsed.password, isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user