feat(base): handles hidden until their people go public, every answer about a new handle counted (ADR 0020, D5)
A handle's page and profiles.byHandle answer as for a handle nobody has until its person goes public: a handle fixed by a publish, or a display name, a bio, a link or an avatar (isPublic). A person's own profile, and the host's own lookups (personWithHandle), are unchanged. So every answer about a new handle now tells that an account has it, and countHandleCheck counts them together on the register, 30 an hour per person (or attempt) and 60 per network, refused beyond with ProfileRefusal.tooManyHandleChecks: profile.checkHandle and saving a profile with a handle new for the person, simpleLogin.checkHandle and signUp, keys.set with a handle, and Halkyon Learn's inbox.block and unblock, which look people up by handle. A person's own handle and what is no handle aren't counted. The SimpleLogin limiters of commit 3 give way to it, so a network's 60 are shared between people signed in and people new. The register's checkHandle, saveProfile, signUpWithSimpleLogin and setFirstKeys carry the visitor's network from the member that asked. Halkyon Learn's handle dialog words the new refusal (English, German). Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
e5b9578916
commit
7eb1961b9f
37 changed files with 674 additions and 134 deletions
File diff suppressed because one or more lines are too long
|
|
@ -719,7 +719,9 @@ class EndpointInbox extends _isc.EndpointRef {
|
||||||
|
|
||||||
/// Keeps the person with [handle] from writing on the caller's published
|
/// Keeps the person with [handle] from writing on the caller's published
|
||||||
/// studysets: what they wrote there, and write, shows to them alone.
|
/// studysets: what they wrote there, and write, shows to them alone.
|
||||||
/// Throws [NotFoundException] for a handle nobody has.
|
/// Throws [NotFoundException] for a handle nobody has; at most 30 an hour
|
||||||
|
/// with the caller's other answers about handles, as checking one
|
||||||
|
/// (`ProfileRefusal.tooManyHandleChecks`).
|
||||||
_ida.Future<void> block(String handle) => caller.callServerEndpoint<void>(
|
_ida.Future<void> block(String handle) => caller.callServerEndpoint<void>(
|
||||||
'inbox',
|
'inbox',
|
||||||
'block',
|
'block',
|
||||||
|
|
@ -727,7 +729,7 @@ class EndpointInbox extends _isc.EndpointRef {
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Lets the person with [handle] write on the caller's published
|
/// Lets the person with [handle] write on the caller's published
|
||||||
/// studysets again.
|
/// studysets again; counted as [block] is.
|
||||||
_ida.Future<void> unblock(String handle) => caller.callServerEndpoint<void>(
|
_ida.Future<void> unblock(String handle) => caller.callServerEndpoint<void>(
|
||||||
'inbox',
|
'inbox',
|
||||||
'unblock',
|
'unblock',
|
||||||
|
|
|
||||||
|
|
@ -541,6 +541,7 @@
|
||||||
"profileProblemLinkInvalid": "Links beginnen mit https:// oder http://.",
|
"profileProblemLinkInvalid": "Links beginnen mit https:// oder http://.",
|
||||||
"profileProblemAvatarInvalid": "Dieses Bild lässt sich nicht verwenden: Wähle ein PNG-, JPEG-, WebP- oder GIF-Bild.",
|
"profileProblemAvatarInvalid": "Dieses Bild lässt sich nicht verwenden: Wähle ein PNG-, JPEG-, WebP- oder GIF-Bild.",
|
||||||
"profileProblemTooManyLinkChecks": "Die Links wurden zu oft geprüft. Versuche es in einer Stunde noch einmal.",
|
"profileProblemTooManyLinkChecks": "Die Links wurden zu oft geprüft. Versuche es in einer Stunde noch einmal.",
|
||||||
|
"profileProblemTooManyHandleChecks": "Es wurden zu viele Handles ausprobiert. Versuche es in einer Stunde noch einmal.",
|
||||||
"publicProfilePicture": "Bild",
|
"publicProfilePicture": "Bild",
|
||||||
"publicProfileChoosePicture": "Bild wählen",
|
"publicProfileChoosePicture": "Bild wählen",
|
||||||
"publicProfileRemovePicture": "Bild entfernen",
|
"publicProfileRemovePicture": "Bild entfernen",
|
||||||
|
|
|
||||||
|
|
@ -612,6 +612,7 @@
|
||||||
"profileProblemLinkInvalid": "Links start with https:// or http://.",
|
"profileProblemLinkInvalid": "Links start with https:// or http://.",
|
||||||
"profileProblemAvatarInvalid": "This picture can't be used: choose a PNG, JPEG, WebP or GIF image.",
|
"profileProblemAvatarInvalid": "This picture can't be used: choose a PNG, JPEG, WebP or GIF image.",
|
||||||
"profileProblemTooManyLinkChecks": "The links were checked too often. Try again in an hour.",
|
"profileProblemTooManyLinkChecks": "The links were checked too often. Try again in an hour.",
|
||||||
|
"profileProblemTooManyHandleChecks": "Too many handles were tried. Try again in an hour.",
|
||||||
"publicProfilePicture": "Picture",
|
"publicProfilePicture": "Picture",
|
||||||
"publicProfileChoosePicture": "Choose picture",
|
"publicProfileChoosePicture": "Choose picture",
|
||||||
"publicProfileRemovePicture": "Remove picture",
|
"publicProfileRemovePicture": "Remove picture",
|
||||||
|
|
|
||||||
|
|
@ -3244,6 +3244,12 @@ abstract class AppLocalizations {
|
||||||
/// **'The links were checked too often. Try again in an hour.'**
|
/// **'The links were checked too often. Try again in an hour.'**
|
||||||
String get profileProblemTooManyLinkChecks;
|
String get profileProblemTooManyLinkChecks;
|
||||||
|
|
||||||
|
/// No description provided for @profileProblemTooManyHandleChecks.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Too many handles were tried. Try again in an hour.'**
|
||||||
|
String get profileProblemTooManyHandleChecks;
|
||||||
|
|
||||||
/// No description provided for @publicProfilePicture.
|
/// No description provided for @publicProfilePicture.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
|
||||||
|
|
@ -1963,6 +1963,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
String get profileProblemTooManyLinkChecks =>
|
String get profileProblemTooManyLinkChecks =>
|
||||||
'Die Links wurden zu oft geprüft. Versuche es in einer Stunde noch einmal.';
|
'Die Links wurden zu oft geprüft. Versuche es in einer Stunde noch einmal.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get profileProblemTooManyHandleChecks =>
|
||||||
|
'Es wurden zu viele Handles ausprobiert. Versuche es in einer Stunde noch einmal.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get publicProfilePicture => 'Bild';
|
String get publicProfilePicture => 'Bild';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1944,6 +1944,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
String get profileProblemTooManyLinkChecks =>
|
String get profileProblemTooManyLinkChecks =>
|
||||||
'The links were checked too often. Try again in an hour.';
|
'The links were checked too often. Try again in an hour.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get profileProblemTooManyHandleChecks =>
|
||||||
|
'Too many handles were tried. Try again in an hour.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get publicProfilePicture => 'Picture';
|
String get publicProfilePicture => 'Picture';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,10 @@ enum ProfileProblem() {
|
||||||
avatarInvalid,
|
avatarInvalid,
|
||||||
|
|
||||||
/// The links were checked too often in the last hour.
|
/// The links were checked too often in the last hour.
|
||||||
tooManyLinkChecks;
|
tooManyLinkChecks,
|
||||||
|
|
||||||
|
/// Too many handles were asked about in the last hour.
|
||||||
|
tooManyHandleChecks;
|
||||||
|
|
||||||
/// The problems with a handle.
|
/// The problems with a handle.
|
||||||
static const handleProblems = {
|
static const handleProblems = {
|
||||||
|
|
@ -118,6 +121,7 @@ enum ProfileProblem() {
|
||||||
handleReserved,
|
handleReserved,
|
||||||
handleTaken,
|
handleTaken,
|
||||||
published,
|
published,
|
||||||
|
tooManyHandleChecks,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -679,20 +679,22 @@ class const _Picture({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _problemText(AppLocalizations l10n, ProfileProblem problem) =>
|
String _problemText(
|
||||||
switch (problem) {
|
AppLocalizations l10n,
|
||||||
ProfileProblem.handleInvalid => l10n.profileProblemHandleInvalid,
|
ProfileProblem problem,
|
||||||
ProfileProblem.handleReserved => l10n.profileProblemHandleReserved,
|
) => switch (problem) {
|
||||||
ProfileProblem.handleTaken => l10n.profileProblemHandleTaken,
|
ProfileProblem.handleInvalid => l10n.profileProblemHandleInvalid,
|
||||||
ProfileProblem.published => l10n.profileProblemPublished,
|
ProfileProblem.handleReserved => l10n.profileProblemHandleReserved,
|
||||||
ProfileProblem.displayNameTooLong =>
|
ProfileProblem.handleTaken => l10n.profileProblemHandleTaken,
|
||||||
l10n.profileProblemDisplayNameTooLong,
|
ProfileProblem.published => l10n.profileProblemPublished,
|
||||||
ProfileProblem.bioTooLong => l10n.profileProblemBioTooLong,
|
ProfileProblem.displayNameTooLong => l10n.profileProblemDisplayNameTooLong,
|
||||||
ProfileProblem.tooManyLinks => l10n.profileProblemTooManyLinks,
|
ProfileProblem.bioTooLong => l10n.profileProblemBioTooLong,
|
||||||
ProfileProblem.linkInvalid => l10n.profileProblemLinkInvalid,
|
ProfileProblem.tooManyLinks => l10n.profileProblemTooManyLinks,
|
||||||
ProfileProblem.avatarInvalid => l10n.profileProblemAvatarInvalid,
|
ProfileProblem.linkInvalid => l10n.profileProblemLinkInvalid,
|
||||||
ProfileProblem.tooManyLinkChecks => l10n.profileProblemTooManyLinkChecks,
|
ProfileProblem.avatarInvalid => l10n.profileProblemAvatarInvalid,
|
||||||
};
|
ProfileProblem.tooManyLinkChecks => l10n.profileProblemTooManyLinkChecks,
|
||||||
|
ProfileProblem.tooManyHandleChecks => l10n.profileProblemTooManyHandleChecks,
|
||||||
|
};
|
||||||
|
|
||||||
/// The profile's page on the web, to link back to, with a way to copy its
|
/// The profile's page on the web, to link back to, with a way to copy its
|
||||||
/// address, and what linking back to it does (ADR 0011).
|
/// address, and what linking back to it does (ADR 0011).
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,27 @@ void main() {
|
||||||
expect(find.text('Saved'), findsOneWidget);
|
expect(find.text('Saved'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('the dialog says when the instance answers no more about '
|
||||||
|
'handles this hour, and nothing can be chosen', (tester) async {
|
||||||
|
final profiles = FakeProfileGateway()..checksSpent = true;
|
||||||
|
await showProfile(tester, profiles);
|
||||||
|
|
||||||
|
await tester.tap(find.text('Choose a handle'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await typeHandle(tester, 'raisondetredev');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.text('Too many handles were tried. Try again in an hour.'),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
tester
|
||||||
|
.widget<FilledButton>(find.widgetWithText(FilledButton, 'Choose'))
|
||||||
|
.onPressed,
|
||||||
|
isNull,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('a profile saves a field when it is left or Enter is pressed, '
|
testWidgets('a profile saves a field when it is left or Enter is pressed, '
|
||||||
'only what changed, without losing what is being written', (
|
'only what changed, without losing what is being written', (
|
||||||
tester,
|
tester,
|
||||||
|
|
|
||||||
|
|
@ -39,13 +39,18 @@ class FakeProfileGateway({
|
||||||
/// The handles checked, in order.
|
/// The handles checked, in order.
|
||||||
final checked = <String>[];
|
final checked = <String>[];
|
||||||
|
|
||||||
|
/// Whether the instance answers no more about handles this hour.
|
||||||
|
bool checksSpent = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ProfileProblem?> checkHandle(String handle) async {
|
Future<ProfileProblem?> checkHandle(String handle) async {
|
||||||
if (fail case final error?) {
|
if (fail case final error?) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
checked.add(handle);
|
checked.add(handle);
|
||||||
return _problemOf(handle);
|
return checksSpent
|
||||||
|
? ProfileProblem.tooManyHandleChecks
|
||||||
|
: _problemOf(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfileProblem? _problemOf(String handle) {
|
ProfileProblem? _problemOf(String handle) {
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,9 @@ class InboxEndpoint extends Endpoint {
|
||||||
|
|
||||||
/// Keeps the person with [handle] from writing on the caller's published
|
/// Keeps the person with [handle] from writing on the caller's published
|
||||||
/// studysets: what they wrote there, and write, shows to them alone.
|
/// studysets: what they wrote there, and write, shows to them alone.
|
||||||
/// Throws [NotFoundException] for a handle nobody has.
|
/// Throws [NotFoundException] for a handle nobody has; at most 30 an hour
|
||||||
|
/// with the caller's other answers about handles, as checking one
|
||||||
|
/// (`ProfileRefusal.tooManyHandleChecks`).
|
||||||
Future<void> block(Session session, String handle) async {
|
Future<void> block(Session session, String handle) async {
|
||||||
final author = session.authenticated!.authUserId;
|
final author = session.authenticated!.authUserId;
|
||||||
final blocked = await _personOf(session, handle);
|
final blocked = await _personOf(session, handle);
|
||||||
|
|
@ -119,7 +121,7 @@ class InboxEndpoint extends Endpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lets the person with [handle] write on the caller's published
|
/// Lets the person with [handle] write on the caller's published
|
||||||
/// studysets again.
|
/// studysets again; counted as [block] is.
|
||||||
Future<void> unblock(Session session, String handle) async {
|
Future<void> unblock(Session session, String handle) async {
|
||||||
final author = session.authenticated!.authUserId;
|
final author = session.authenticated!.authUserId;
|
||||||
final blocked = await _personOf(session, handle);
|
final blocked = await _personOf(session, handle);
|
||||||
|
|
@ -195,10 +197,19 @@ class InboxEndpoint extends Endpoint {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The person with [handle]; throws [NotFoundException] for none.
|
/// The person with [handle]; throws [NotFoundException] for none. Counted
|
||||||
Future<UuidValue> _personOf(Session session, String handle) async =>
|
/// with the caller's other answers about handles (`countHandleCheck`),
|
||||||
await personWithHandle(session, handle.trim()) ??
|
/// since whose a handle is tells that an account has it, while handles
|
||||||
(throw NotFoundException(what: 'person'));
|
/// stay hidden until their people go public (ADR 0020, D5).
|
||||||
|
Future<UuidValue> _personOf(Session session, String handle) async {
|
||||||
|
await countHandleCheck(
|
||||||
|
session,
|
||||||
|
asker: session.authenticated!.authUserId.uuid,
|
||||||
|
network: networkOf(session),
|
||||||
|
);
|
||||||
|
return await personWithHandle(session, handle.trim()) ??
|
||||||
|
(throw NotFoundException(what: 'person'));
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the thread's [payload] names the file [sha256] among those of
|
/// Whether the thread's [payload] names the file [sha256] among those of
|
||||||
/// its proposed content (`files`).
|
/// its proposed content (`files`).
|
||||||
|
|
|
||||||
|
|
@ -261,6 +261,33 @@ void main() {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test('blocking and unblocking count among the answers about handles '
|
||||||
|
'(30 an hour), as whose a handle is tells that an account has '
|
||||||
|
'it', () async {
|
||||||
|
// Her handle, new as her profile was made, counted one.
|
||||||
|
for (var i = 1; i < handleChecksPerAsker; i++) {
|
||||||
|
await countHandleCheck(session, asker: alice.uuid, network: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final call in [
|
||||||
|
() => endpoints.inbox.block(signedIn(alice), 'bob'),
|
||||||
|
() => endpoints.inbox.unblock(signedIn(alice), 'bob'),
|
||||||
|
]) {
|
||||||
|
await expectLater(
|
||||||
|
call(),
|
||||||
|
throwsA(
|
||||||
|
isA<ProfileRefusedException>().having(
|
||||||
|
(error) => error.reason,
|
||||||
|
'reason',
|
||||||
|
ProfileRefusal.tooManyHandleChecks,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
expect(await endpoints.inbox.blockedPeople(signedIn(alice)), isEmpty);
|
||||||
|
await endpoints.inbox.block(signedIn(bob), 'alice');
|
||||||
|
});
|
||||||
|
|
||||||
test("a suggestion's files go to the author alone", () async {
|
test("a suggestion's files go to the author alone", () async {
|
||||||
final publication = await japanese();
|
final publication = await japanese();
|
||||||
final image = utf8.encode('an image');
|
final image = utf8.encode('an image');
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,17 @@
|
||||||
decoy; mending ends every session of the account, as whoever came in through the old
|
decoy; mending ends every session of the account, as whoever came in through the old
|
||||||
link may still be in; and SimpleLogin needs `SIGN_IN_DECOY_KEY` alone of ADR 0017's
|
link may still be in; and SimpleLogin needs `SIGN_IN_DECOY_KEY` alone of ADR 0017's
|
||||||
three keys, a server with a SimpleLogin app and without it stopping as it starts.
|
three keys, a server with a SimpleLogin app and without it stopping as it starts.
|
||||||
|
Then handles (9, D5): `/@handle` and `profiles.byHandle` answer as for a handle nobody
|
||||||
|
has until its person goes public (a handle fixed by a publish, or a display name, a
|
||||||
|
bio, a link or an avatar), the host's own code still finding whose a handle is.
|
||||||
|
Otherwise than proposed: not only asking whether a handle is free is limited, but
|
||||||
|
every answer about a new handle, counted together (`countHandleCheck`, 30 an hour per
|
||||||
|
person or attempt, 60 per network): saving a profile with one, signing up and the
|
||||||
|
first keys with one, and Halkyon Learn's blocks by handle, as each tells the same, so
|
||||||
|
a limit on checking alone would limit nothing; a person's own handle and what is no
|
||||||
|
handle aren't counted. The handle suggested at sign-up is not counted: it tells at
|
||||||
|
most that the name SimpleLogin shared is someone's handle already, once a sign-in.
|
||||||
|
Step 3's deviations accepted by the developer (23 Oct, night).
|
||||||
- **Builds on:** ADR 0010 (email sign-in), ADR 0011 (profile pages), ADR 0016 (the
|
- **Builds on:** ADR 0010 (email sign-in), ADR 0011 (profile pages), ADR 0016 (the
|
||||||
register), ADR 0017 (a password, TOTP and the keys).
|
register), ADR 0017 (a password, TOTP and the keys).
|
||||||
- **Changes:** ADR 0017's way in on an instance that offers SimpleLogin (its keys, their
|
- **Changes:** ADR 0017's way in on an instance that offers SimpleLogin (its keys, their
|
||||||
|
|
|
||||||
|
|
@ -287,8 +287,8 @@ class EndpointKeys extends _isc.EndpointRef {
|
||||||
/// ten minutes on this device, under [attempt]. Refused for an account
|
/// ten minutes on this device, under [attempt]. Refused for an account
|
||||||
/// with keys (`PasswordRefusal.passwordExists`), a setup out of range
|
/// with keys (`PasswordRefusal.passwordExists`), a setup out of range
|
||||||
/// (`PasswordRefusal.invalidSetup`), a handle refused or missing
|
/// (`PasswordRefusal.invalidSetup`), a handle refused or missing
|
||||||
/// (`ProfileRefusedException`), and without that sign-in
|
/// (`ProfileRefusedException`; a handle counted as `profile.checkHandle`
|
||||||
/// (`OidcRefusal.unknownAttempt`).
|
/// counts it), and without that sign-in (`OidcRefusal.unknownAttempt`).
|
||||||
_ida.Future<void> set({
|
_ida.Future<void> set({
|
||||||
required _isc.UuidValue attempt,
|
required _isc.UuidValue attempt,
|
||||||
required _iec2fqr0.KeysSetup setup,
|
required _iec2fqr0.KeysSetup setup,
|
||||||
|
|
@ -790,7 +790,8 @@ class EndpointProfile extends _isc.EndpointRef {
|
||||||
/// Saves your public profile, making it if you have none. Throws
|
/// Saves your public profile, making it if you have none. Throws
|
||||||
/// [ProfileRefusedException] when it can't be saved (see
|
/// [ProfileRefusedException] when it can't be saved (see
|
||||||
/// [ProfileRefusal]): a handle someone else has, whatever its case, or a
|
/// [ProfileRefusal]): a handle someone else has, whatever its case, or a
|
||||||
/// new handle once yours is fixed.
|
/// new handle once yours is fixed. A new handle counts as [checkHandle]
|
||||||
|
/// counts it.
|
||||||
_ida.Future<_i8qdmv6n.Profile> save(
|
_ida.Future<_i8qdmv6n.Profile> save(
|
||||||
String handle,
|
String handle,
|
||||||
String displayName,
|
String displayName,
|
||||||
|
|
@ -823,9 +824,13 @@ class EndpointProfile extends _isc.EndpointRef {
|
||||||
/// Why [handle] couldn't be yours, as saving it would refuse it: not a
|
/// Why [handle] couldn't be yours, as saving it would refuse it: not a
|
||||||
/// handle, reserved for the instance, someone else's whatever its case,
|
/// handle, reserved for the instance, someone else's whatever its case,
|
||||||
/// or a new one once yours is fixed; null when it can, as your own can. For
|
/// or a new one once yours is fixed; null when it can, as your own can. For
|
||||||
/// the app to say so while it is typed; saving checks again. Whose a handle
|
/// the app to say so while it is typed; saving checks again. Handles stay
|
||||||
/// is, anyone can find out already ([ProfilesEndpoint.byHandle]), so this
|
/// hidden until their people go public (Halkyon Learn's ADR 0020, D5), so
|
||||||
/// tells nothing new.
|
/// this tells that an account exists: at most 30 an hour for you and 60
|
||||||
|
/// from your network, with every other answer about a new handle (saving
|
||||||
|
/// one, signing up with one), refused beyond
|
||||||
|
/// ([ProfileRefusal.tooManyHandleChecks], thrown); your own handle and
|
||||||
|
/// what is no handle aren't counted.
|
||||||
_ida.Future<_ih722tf9.ProfileRefusal?> checkHandle(String handle) =>
|
_ida.Future<_ih722tf9.ProfileRefusal?> checkHandle(String handle) =>
|
||||||
caller.callServerEndpoint<_ih722tf9.ProfileRefusal?>(
|
caller.callServerEndpoint<_ih722tf9.ProfileRefusal?>(
|
||||||
'halkyon_base.profile',
|
'halkyon_base.profile',
|
||||||
|
|
@ -873,7 +878,10 @@ class EndpointProfiles extends _isc.EndpointRef {
|
||||||
String get name => 'halkyon_base.profiles';
|
String get name => 'halkyon_base.profiles';
|
||||||
|
|
||||||
/// The public profile with [handle], whatever its case. Throws
|
/// The public profile with [handle], whatever its case. Throws
|
||||||
/// [ProfileNotFoundException] when there is none.
|
/// [ProfileNotFoundException] when there is none, and as well while its
|
||||||
|
/// person hasn't gone public (Halkyon Learn's ADR 0020, D5): published
|
||||||
|
/// nothing and left the rest of the profile empty (display name, bio,
|
||||||
|
/// links, avatar).
|
||||||
_ida.Future<_i8qdmv6n.Profile> byHandle(String handle) =>
|
_ida.Future<_i8qdmv6n.Profile> byHandle(String handle) =>
|
||||||
caller.callServerEndpoint<_i8qdmv6n.Profile>(
|
caller.callServerEndpoint<_i8qdmv6n.Profile>(
|
||||||
'halkyon_base.profiles',
|
'halkyon_base.profiles',
|
||||||
|
|
@ -931,9 +939,10 @@ class EndpointSimpleLogin extends _isc.EndpointRef {
|
||||||
/// said `newcomer`), could take [handle]: null where they could, else why
|
/// said `newcomer`), could take [handle]: null where they could, else why
|
||||||
/// not, as saving a profile would say. For the app to say so while it is
|
/// not, as saving a profile would say. For the app to say so while it is
|
||||||
/// typed; [signUp] checks again. At most 30 for an attempt and 60 from a
|
/// typed; [signUp] checks again. At most 30 for an attempt and 60 from a
|
||||||
/// network in an hour (`ProfileRefusal.tooManyHandleChecks`, thrown):
|
/// network in an hour, with every other answer about a handle
|
||||||
/// each answer tells which handles exist. Refused
|
/// (`ProfileRefusal.tooManyHandleChecks`, thrown): each tells which
|
||||||
/// (`OidcRefusal.unknownAttempt`) where no identity waits.
|
/// handles exist. Refused (`OidcRefusal.unknownAttempt`) where no identity
|
||||||
|
/// waits.
|
||||||
_ida.Future<_ih722tf9.ProfileRefusal?> checkHandle({
|
_ida.Future<_ih722tf9.ProfileRefusal?> checkHandle({
|
||||||
required _isc.UuidValue attempt,
|
required _isc.UuidValue attempt,
|
||||||
required String handle,
|
required String handle,
|
||||||
|
|
@ -952,9 +961,10 @@ class EndpointSimpleLogin extends _isc.EndpointRef {
|
||||||
/// keys [setup] describes, all made on the device beforehand; a session
|
/// keys [setup] describes, all made on the device beforehand; a session
|
||||||
/// for it here. Refused, the identity still waiting: for a setup out of
|
/// for it here. Refused, the identity still waiting: for a setup out of
|
||||||
/// range (`PasswordRefusal.invalidSetup`) and a handle refused
|
/// range (`PasswordRefusal.invalidSetup`) and a handle refused
|
||||||
/// (`ProfileRefusedException`, as saving a profile refuses it); for an
|
/// (`ProfileRefusedException`, as saving a profile refuses it, the handle
|
||||||
/// attempt unknown, taken or past its time (`OidcRefusal.unknownAttempt`),
|
/// counted as [checkHandle] counts it); for an attempt unknown, taken or
|
||||||
/// and called signed in (`OidcRefusal.wrongCaller`).
|
/// past its time (`OidcRefusal.unknownAttempt`), and called signed in
|
||||||
|
/// (`OidcRefusal.wrongCaller`).
|
||||||
_ida.Future<_iacc.AuthSuccess> signUp({
|
_ida.Future<_iacc.AuthSuccess> signUp({
|
||||||
required _isc.UuidValue attempt,
|
required _isc.UuidValue attempt,
|
||||||
required String handle,
|
required String handle,
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,9 @@ enum ProfileRefusal implements _isc.SerializableModel {
|
||||||
/// The links were checked too often in the last hour.
|
/// The links were checked too often in the last hour.
|
||||||
tooManyLinkChecks,
|
tooManyLinkChecks,
|
||||||
|
|
||||||
/// The handles were checked too often in the last hour: their answers
|
/// Too many answers about new handles in the last hour (checking them,
|
||||||
/// tell which handles exist.
|
/// saving, signing up or blocking with them): each tells which handles
|
||||||
|
/// exist.
|
||||||
tooManyHandleChecks;
|
tooManyHandleChecks;
|
||||||
|
|
||||||
static ProfileRefusal fromJson(String name) {
|
static ProfileRefusal fromJson(String name) {
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ Endpoints, under the nickname `base` in a host's client (`client.modules.base.em
|
||||||
| `emailCode` | `sendCode`: a code by mail, which proves the address and signs nobody in | anyone |
|
| `emailCode` | `sendCode`: a code by mail, which proves the address and signs nobody in | anyone |
|
||||||
| `account` | `delete` (with the password's proof where the account has one, `keysProof` for one that signs in with SimpleLogin), `signInWays` | signed in |
|
| `account` | `delete` (with the password's proof where the account has one, `keysProof` for one that signs in with SimpleLogin), `signInWays` | signed in |
|
||||||
| `instance` | `basics`: the app it serves, GitHub's client ID if any and when GitHub sign-in ends, whether codes by mail, password sign-in and SimpleLogin are on, the source link, the oldest app build, where data and backups are | anyone |
|
| `instance` | `basics`: the app it serves, GitHub's client ID if any and when GitHub sign-in ends, whether codes by mail, password sign-in and SimpleLogin are on, the source link, the oldest app build, where data and backups are | anyone |
|
||||||
| `profile` | `mine`, `save`, `checkHandle`, `checkLinks` (at most 10 an hour), `setAvatar`, `removeAvatar`, `remove`: the caller's own public profile | `identified` |
|
| `profile` | `mine`, `save`, `checkHandle` (with every answer about a new handle, at most 30 an hour per person and 60 per network), `checkLinks` (at most 10 an hour), `setAvatar`, `removeAvatar`, `remove`: the caller's own public profile | `identified` |
|
||||||
| `profiles` | `byHandle`: anyone's public profile, whatever the handle's case | signed in |
|
| `profiles` | `byHandle`: anyone's public profile once they went public, whatever the handle's case | signed in |
|
||||||
|
|
||||||
- GitHub keeps only the numeric GitHub ID, in Serverpod's own table; email keeps only
|
- GitHub keeps only the numeric GitHub ID, in Serverpod's own table; email keeps only
|
||||||
HMAC-SHA256 of the address, keyed with `emailSignInPepper` (`email_identity`), and its
|
HMAC-SHA256 of the address, keyed with `emailSignInPepper` (`email_identity`), and its
|
||||||
|
|
@ -44,7 +44,16 @@ Endpoints, under the nickname `base` in a host's client (`client.modules.base.em
|
||||||
in the public storage at `avatars/<SHA-256>.png`. The page is at
|
in the public storage at `avatars/<SHA-256>.png`. The page is at
|
||||||
`https://<account domain>/@handle` (`ACCOUNT_DOMAIN`, else the server's public host),
|
`https://<account domain>/@handle` (`ACCOUNT_DOMAIN`, else the server's public host),
|
||||||
served by `addProfilePageRoute`, in English or German, its foot what the host's
|
served by `addProfilePageRoute`, in English or German, its foot what the host's
|
||||||
`profilePageNote` says.
|
`profilePageNote` says. A handle stays its person's alone until they go public
|
||||||
|
(Halkyon Learn's ADR 0020, D5: a handle fixed, or a display name, bio, link or
|
||||||
|
avatar): until then its page and `profiles.byHandle` answer as for a handle nobody
|
||||||
|
has, while the host's own code still finds whose it is (`personWithHandle`). Every
|
||||||
|
answer about a new handle tells whether an account has it, so they are counted
|
||||||
|
together (`countHandleCheck`, on the register: checking one, saving one, signing up
|
||||||
|
or setting first keys with one, and the host's own lookups, such as Halkyon Learn's
|
||||||
|
blocks), at most 30 an hour per person (or attempt) and 60 per network
|
||||||
|
(`tooManyHandleChecks`); a person's own handle and what is no handle aren't
|
||||||
|
counted.
|
||||||
- SimpleLogin (Halkyon Learn's ADR 0020) is halkyon_oidc's sign-in (ADR 0023) with its
|
- SimpleLogin (Halkyon Learn's ADR 0020) is halkyon_oidc's sign-in (ADR 0023) with its
|
||||||
SimpleLogin preset, on the register alone, which holds the instance's SimpleLogin app
|
SimpleLogin preset, on the register alone, which holds the instance's SimpleLogin app
|
||||||
and exchanges every code; a member asks it to begin and to complete, and issues its own
|
and exchanges every code; a member asks it to begin and to complete, and issues its own
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ export 'src/password/passwords.dart'
|
||||||
signInChallengeLifetime,
|
signInChallengeLifetime,
|
||||||
signsInWithPassword,
|
signsInWithPassword,
|
||||||
wrappedKeysLength;
|
wrappedKeysLength;
|
||||||
|
export 'src/profile/handle_checks.dart';
|
||||||
export 'src/profile/link_verification.dart';
|
export 'src/profile/link_verification.dart';
|
||||||
export 'src/profile/profile_page.dart' show addProfilePageRoute, profilePage;
|
export 'src/profile/profile_page.dart' show addProfilePageRoute, profilePage;
|
||||||
export 'src/profile/profile_rules.dart'
|
export 'src/profile/profile_rules.dart'
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,9 @@ enum ProfileRefusal implements _is.SerializableModel {
|
||||||
/// The links were checked too often in the last hour.
|
/// The links were checked too often in the last hour.
|
||||||
tooManyLinkChecks,
|
tooManyLinkChecks,
|
||||||
|
|
||||||
/// The handles were checked too often in the last hour: their answers
|
/// Too many answers about new handles in the last hour (checking them,
|
||||||
/// tell which handles exist.
|
/// saving, signing up or blocking with them): each tells which handles
|
||||||
|
/// exist.
|
||||||
tooManyHandleChecks;
|
tooManyHandleChecks;
|
||||||
|
|
||||||
static ProfileRefusal fromJson(String name) {
|
static ProfileRefusal fromJson(String name) {
|
||||||
|
|
|
||||||
|
|
@ -53,14 +53,16 @@ Future<DeviceKeys> unlockKeys(
|
||||||
/// ([PasswordRefusal.invalidSetup]) and an account with keys already, under
|
/// ([PasswordRefusal.invalidSetup]) and an account with keys already, under
|
||||||
/// either kind of password ([PasswordRefusal.passwordExists]);
|
/// either kind of password ([PasswordRefusal.passwordExists]);
|
||||||
/// ([ProfileRefusedException]) for a handle refused as saving a profile
|
/// ([ProfileRefusedException]) for a handle refused as saving a profile
|
||||||
/// refuses it, and one missing where the account has none or given where it
|
/// refuses it (a handle counted as checking it is, from [network]), and one
|
||||||
/// has one ([ProfileRefusal.handleInvalid]); the sign-in's refusals as
|
/// missing where the account has none or given where it has one
|
||||||
|
/// ([ProfileRefusal.handleInvalid]); the sign-in's refusals as
|
||||||
/// [takeSimpleLoginConfirmation]'s. Nothing changes then.
|
/// [takeSimpleLoginConfirmation]'s. Nothing changes then.
|
||||||
Future<void> setFirstKeys(
|
Future<void> setFirstKeys(
|
||||||
Session session, {
|
Session session, {
|
||||||
required UuidValue person,
|
required UuidValue person,
|
||||||
required UuidValue attempt,
|
required UuidValue attempt,
|
||||||
required KeysSetup setup,
|
required KeysSetup setup,
|
||||||
|
required String network,
|
||||||
String? handle,
|
String? handle,
|
||||||
}) async {
|
}) async {
|
||||||
if (!isKeysSetup(setup)) {
|
if (!isKeysSetup(setup)) {
|
||||||
|
|
@ -87,7 +89,13 @@ Future<void> setFirstKeys(
|
||||||
try {
|
try {
|
||||||
await session.db.transaction((transaction) async {
|
await session.db.transaction((transaction) async {
|
||||||
if (handle != null) {
|
if (handle != null) {
|
||||||
if (await refusalOfNewHandle(session, handle, transaction: transaction)
|
if (await refusalOfNewHandle(
|
||||||
|
session,
|
||||||
|
handle,
|
||||||
|
asker: person.uuid,
|
||||||
|
network: network,
|
||||||
|
transaction: transaction,
|
||||||
|
)
|
||||||
case final reason?) {
|
case final reason?) {
|
||||||
throw ProfileRefusedException(reason: reason);
|
throw ProfileRefusedException(reason: reason);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue