nythos-core v0.2.0
Summary
nythos-core v0.2.0 adds the identity profile and login identifier foundation.
This release introduces optional username and display-name support, tenant-policy-gated profile fields, and identifier-based login while keeping email/password accounts intact and preserving the core crate’s infrastructure-free boundary.
OAuth is not part of this release.
Highlights
Added new domain value objects:
Username
DisplayName
LoginIdentifier
Added typed tenant auth policy:
TenantAuthPolicy
username_registration_enabled
display_name_registration_enabled
username_login_enabled
Added TenantPolicyPort for loading tenant auth policy inside core services.
Extended User with optional profile fields:
username: Option<Username>
display_name: Option<DisplayName>
Extended NewUser with optional profile fields while preserving NewUser::new(email).
Extended RegisterInput with optional raw profile input:
username: Option<String>
display_name: Option<String>
Updated RegisterService to enforce tenant profile policy:
username registration is accepted only when enabled
display-name registration is accepted only when enabled
duplicate username checks are tenant-scoped
email/password registration still works with default policy when no profile fields are supplied
Moved LoginInput toward identifier-based login:
preserved LoginInput::new(..., email: String, ...)
added LoginInput::new_with_identifier(...)
added identifier()
preserved email() as a compatibility alias
Updated LoginService to support LoginIdentifier:
email login uses find_credentials_by_email
username login uses find_credentials_by_username only when enabled
disabled username login returns InvalidCredentials
username credential lookup is not called when username login is disabled
Evolved UserRepository with username lookup methods:
find_by_username
find_credentials_by_username
Added ADR 0006 documenting login identifiers and optional profile fields.
Updated README and core docs for v0.2.0 behavior.
Why this changed
Nythos needs a foundation for richer identity profiles and future product-facing login options without weakening the core auth boundary.
Email remains the required credential identity for email/password accounts. Username and display name are optional profile fields. Username-based login is available only when a tenant explicitly enables it through TenantAuthPolicy.
This release keeps policy decisions visible in the service layer. Services parse LoginIdentifier, load tenant auth policy through TenantPolicyPort, and then call explicit repository methods. Repositories resolve concrete lookup keys only.
This avoids a vague find_credentials_by_identifier-style repository method where parsing, policy checks, and lookup behavior would be hidden behind one swampy abstraction.
Security behavior
Username login is disabled by default.
When username login is disabled, LoginService returns AuthError::InvalidCredentials and does not call username credential lookup.
The following cases intentionally collapse to the same public error:
username login disabled
username not found
wrong password
This prevents callers from using login responses to discover whether username login is enabled or whether a tenant-scoped username exists.
Breaking API shape changes
This release includes public API shape changes for port implementors and service constructors.
UserRepository implementors must now implement:
async fn find_by_username(
&self,
tenant_id: TenantId,
username: &Username,
) -> NythosResult<Option>;
async fn find_credentials_by_username(
&self,
tenant_id: TenantId,
username: &Username,
) -> NythosResult<Option>;
RegisterService::new now requires a TenantPolicyPort dependency.
LoginService::new now requires a TenantPolicyPort dependency.
Example:
let register_service = RegisterService::new(
&user_repository,
&tenant_policy_port,
&session_store,
&password_hasher,
&token_signer,
);
let login_service = LoginService::new(
&user_repository,
&role_repository,
&tenant_policy_port,
&session_store,
&password_hasher,
&token_signer,
);
Compatibility notes
Email/password registration still works with the default tenant auth policy when no optional profile fields are supplied.
LoginInput::new(..., email: String, ...) is preserved.
LoginInput::email() is preserved as a compatibility alias returning the raw identifier string.
LoginInput::new_with_identifier(...) is available for email-or-username login input.
User::new(...) and User::with_status(...) are preserved and set optional profile fields to None.
NewUser::new(email) is preserved and sets optional profile fields to None.
TenantSettings remains available, but it is not used for auth policy decisions.
TenantAuthPolicy defaults all optional profile and username-login features to false.
No HTTP, database, gateway, worker, OAuth, Cloudflare, or concrete crypto dependency was added to nythos-core.
Service structs remain borrow-oriented and continue taking dependencies by reference.