feat(v1.1.8): UsersService trait + impl — CRUD + login/verify/logout
UsersService trait in shared::users + Postgres-backed impl in
manager-core::users_service. Wired into the Services bundle (new
`users` field) and the picloud binary's startup.
Commit 4 ships:
* CRUD: create / get / find_by_email / update / delete / list
(cursor-paged on created_at). create validates email shape,
8-char password minimum, optional display_name; throws
DuplicateEmail on (app_id, lower(email)) collision.
* Auth: login (timing-flat — runs verify_password against the
shared dummy Argon2id PHC even on email miss, so the
bad-email and good-email branches share wall-clock cost);
verify (sliding TTL bump capped at absolute_expires_at);
logout (revoke by token).
* verify_session_for_realtime — non-SDK method taking app_id
explicitly; used by the F3 realtime auth_mode='session' path
in a later commit.
* Cross-app isolation everywhere: cx.app_id (or explicit app_id)
is the only source of truth; a logout for a foreign-app token
is a silent no-op rather than a probe of session existence.
Email-tied flows (verification, password reset, invitations), roles,
and admin-mediated helpers are stubbed UsersError::Backend and ship
in later commits in this series.
Config (UsersServiceConfig) sources from env with documented
defaults:
PICLOUD_APP_USER_SESSION_TTL_HOURS (24)
PICLOUD_APP_USER_SESSION_ABSOLUTE_HOURS (720 = 30d)
PICLOUD_APP_USER_VERIFICATION_TTL_HOURS (48)
PICLOUD_APP_USER_PASSWORD_RESET_TTL_HOURS (1)
PICLOUD_APP_USER_INVITATION_TTL_DAYS (7)
Reserved a TIMING_FLAT_DUMMY_HASH constant on manager-core::auth so
the dummy PHC is one shared definition.
Added AppUserId + InvitationId id types in picloud-shared.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,8 @@ use crate::{
|
||||
DeadLetterService, DocsService, EmailService, FilesService, HttpService, KvService,
|
||||
ModuleSource, NoopDeadLetterService, NoopDocsService, NoopEmailService, NoopEventEmitter,
|
||||
NoopFilesService, NoopHttpService, NoopKvService, NoopModuleSource, NoopPubsubService,
|
||||
NoopSecretsService, PubsubService, SecretsService, ServiceEventEmitter,
|
||||
NoopSecretsService, NoopUsersService, PubsubService, SecretsService, ServiceEventEmitter,
|
||||
UsersService,
|
||||
};
|
||||
|
||||
/// SDK service bundle. See module docs for the lifecycle and the v1.1.x
|
||||
@@ -86,6 +87,14 @@ pub struct Services {
|
||||
/// `NoopEmailService` (always `NotConfigured`) in tests that don't
|
||||
/// send mail.
|
||||
pub email: Arc<dyn EmailService>,
|
||||
|
||||
/// Per-app data-plane user management (v1.1.8). Scripts get the
|
||||
/// full `users::*` namespace (CRUD, login/verify/logout, email
|
||||
/// verification, password reset, invitations, string-tagged
|
||||
/// roles). Backed by Postgres-stored Argon2id hashes + SHA-256
|
||||
/// session tokens in the picloud binary; `NoopUsersService` in
|
||||
/// tests that don't exercise users.
|
||||
pub users: Arc<dyn UsersService>,
|
||||
}
|
||||
|
||||
impl Services {
|
||||
@@ -105,6 +114,7 @@ impl Services {
|
||||
pubsub: Arc<dyn PubsubService>,
|
||||
secrets: Arc<dyn SecretsService>,
|
||||
email: Arc<dyn EmailService>,
|
||||
users: Arc<dyn UsersService>,
|
||||
) -> Self {
|
||||
Self {
|
||||
kv,
|
||||
@@ -117,6 +127,7 @@ impl Services {
|
||||
pubsub,
|
||||
secrets,
|
||||
email,
|
||||
users,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +149,7 @@ impl Services {
|
||||
Arc::new(NoopPubsubService),
|
||||
Arc::new(NoopSecretsService),
|
||||
Arc::new(NoopEmailService),
|
||||
Arc::new(NoopUsersService),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user