Skip to content

nythos-core v0.2.0

Choose a tag to compare

@Emagjby Emagjby released this 07 Jun 12:57
· 11 commits to main since this release
e6d5226

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.