| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- using Inversion;
- using Invercargill.DataStructures;
- namespace Spry.Authentication {
- /**
- * Error domain for user-related operations.
- */
- public errordomain UserError {
- USER_NOT_FOUND,
- DUPLICATE_USERNAME,
- DUPLICATE_EMAIL,
- INVALID_PASSWORD,
- INVALID_CREDENTIALS,
- USER_INACTIVE,
- PERMISSION_DENIED,
- STORAGE_ERROR
- }
- /**
- * UserService provides user management operations including CRUD,
- * password hashing, and authentication.
- *
- * This service uses the inject<> pattern for dependency injection.
- * All methods are async to work with the repository async API.
- */
- public class UserService : GLib.Object {
- private UserRepository _repository = inject<UserRepository>();
- // =========================================================================
- // User Creation
- // =========================================================================
- /**
- * Creates a new user with the specified credentials.
- *
- * This method:
- * - Validates username uniqueness
- * - Validates email uniqueness
- * - Hashes password with Argon2id via libsodium
- * - Creates User with UUID and timestamps
- *
- * @param username The unique username
- * @param email The unique email address
- * @param password The plaintext password to hash
- * @return The created User
- * @throws UserError on validation or storage failure
- */
- public async User create_user_async(string username, string email, string password) throws Error {
- // Validate username uniqueness
- if (yield username_exists_async(username)) {
- throw new UserError.DUPLICATE_USERNAME("Username already exists");
- }
- // Validate email uniqueness
- if (yield email_exists_async(email)) {
- throw new UserError.DUPLICATE_EMAIL("Email already exists");
- }
- // Hash password with Argon2id
- var password_hash = hash_password(password);
- if (password_hash == null) {
- throw new UserError.STORAGE_ERROR("Failed to hash password");
- }
- // Create user via repository
- var user = yield _repository.create(username, email, (!)password_hash);
- return user;
- }
- // =========================================================================
- // User Retrieval
- // =========================================================================
- /**
- * Gets a user by their unique ID.
- *
- * @param user_id The user's unique identifier
- * @return The User, or null if not found
- * @throws Error on storage failure
- */
- public async User? get_user_async(string user_id) throws Error {
- return yield _repository.get_by_id(user_id);
- }
- /**
- * Gets a user by their username.
- *
- * @param username The username to look up
- * @return The User, or null if not found
- * @throws Error on storage failure
- */
- public async User? get_user_by_username_async(string username) throws Error {
- return yield _repository.get_by_username(username);
- }
- /**
- * Gets a user by their email address.
- *
- * @param email The email address to look up
- * @return The User, or null if not found
- * @throws Error on storage failure
- */
- public async User? get_user_by_email_async(string email) throws Error {
- return yield _repository.get_by_email(email);
- }
- // =========================================================================
- // User Update
- // =========================================================================
- /**
- * Updates an existing user.
- *
- * This method:
- * - Updates the updated_at timestamp
- * - Handles username/email changes with uniqueness validation
- *
- * @param user The user to update
- * @throws Error on validation or storage failure
- */
- public async void update_user_async(User user) throws Error {
- // Get existing user to check for changes
- var existing = yield get_user_async(user.id);
- if (existing == null) {
- throw new UserError.USER_NOT_FOUND("User not found");
- }
- // Check if username changed
- if (existing.username != user.username) {
- // Check new username uniqueness
- var existing_with_username = yield get_user_by_username_async(user.username);
- if (existing_with_username != null && existing_with_username.id != user.id) {
- throw new UserError.DUPLICATE_USERNAME("Username already exists");
- }
- }
- // Check if email changed
- if (existing.email != user.email) {
- // Check new email uniqueness
- var existing_with_email = yield get_user_by_email_async(user.email);
- if (existing_with_email != null && existing_with_email.id != user.id) {
- throw new UserError.DUPLICATE_EMAIL("Email already exists");
- }
- }
- // Update timestamp
- user.updated_at = new DateTime.now_utc();
- // Store updated user via repository
- yield _repository.update(user);
- }
- // =========================================================================
- // User Deletion
- // =========================================================================
- /**
- * Deletes a user by their unique ID.
- *
- * @param user_id The user's unique identifier
- * @throws Error on storage failure
- */
- public async void delete_user_async(string user_id) throws Error {
- // Get user first (optional, for logging/cleanup)
- var user = yield get_user_async(user_id);
- if (user == null) {
- throw new UserError.USER_NOT_FOUND("User not found");
- }
- // Delete user via repository
- yield _repository.delete(user_id);
- }
- // =========================================================================
- // User Listing
- // =========================================================================
- /**
- * Lists users with pagination support.
- *
- * Note: This method is not supported by the basic UserRepository interface.
- * Subclasses or extensions should implement this as needed.
- *
- * @param offset The number of users to skip
- * @param limit The maximum number of users to return
- * @return A Vector of users
- * @throws Error on storage failure
- */
- public async Vector<User> list_users_async(int offset = 0, int limit = 100) throws Error {
- // The basic UserRepository interface doesn't include list operations
- // This would need to be added to the interface or handled differently
- // For now, return an empty list as a placeholder
- return new Vector<User>();
- }
- // =========================================================================
- // Password Management
- // =========================================================================
- /**
- * Hashes a password using Argon2id via libsodium.
- *
- * @param password The plaintext password to hash
- * @return The hashed password string, or null on failure
- */
- public string? hash_password(string password) {
- return Sodium.PasswordHashing.hash(password);
- }
- /**
- * Verifies a password against a stored hash.
- *
- * @param user The user to verify against
- * @param password The plaintext password to verify
- * @return true if the password matches, false otherwise
- */
- public bool verify_password(User user, string password) {
- return Sodium.PasswordHashing.check(user.password_hash, password);
- }
- /**
- * Sets a new password for a user.
- *
- * @param user The user to update
- * @param new_password The new plaintext password
- * @throws Error on failure
- */
- public async void set_password_async(User user, string new_password) throws Error {
- var password_hash = hash_password(new_password);
- if (password_hash == null) {
- throw new UserError.STORAGE_ERROR("Failed to hash password");
- }
- user.password_hash = (!)password_hash;
- user.updated_at = new DateTime.now_utc();
- yield update_user_async(user);
- }
- // =========================================================================
- // Authentication
- // =========================================================================
- /**
- * Authenticates a user by username/email and password.
- *
- * This method:
- * - Looks up user by username or email
- * - Verifies the password
- * - Returns the user if valid
- *
- * @param username_or_email The username or email address
- * @param password The plaintext password
- * @return The authenticated User, or null if authentication failed
- * @throws Error on storage failure
- */
- public async User? authenticate_async(string username_or_email, string password) throws Error {
- // Try to find user by username first, then by email
- User? user = yield get_user_by_username_async(username_or_email);
- if (user == null) {
- user = yield get_user_by_email_async(username_or_email);
- }
- if (user == null) {
- return null;
- }
- // Verify password
- bool password_valid = verify_password(user, password);
-
- if (!password_valid) {
- return null;
- }
- return user;
- }
- // =========================================================================
- // Utility Methods
- // =========================================================================
- /**
- * Checks if a username already exists.
- *
- * @param username The username to check
- * @return true if the username exists
- * @throws Error on storage failure
- */
- public async bool username_exists_async(string username) throws Error {
- return yield _repository.exists_by_username(username);
- }
- /**
- * Checks if an email already exists.
- *
- * @param email The email to check
- * @return true if the email exists
- * @throws Error on storage failure
- */
- public async bool email_exists_async(string email) throws Error {
- return yield _repository.exists_by_email(email);
- }
- /**
- * Gets the total count of users.
- *
- * Note: This method is not supported by the basic UserRepository interface.
- * Subclasses or extensions should implement this as needed.
- *
- * @return The number of users
- * @throws Error on storage failure
- */
- public async int user_count_async() throws Error {
- // The basic UserRepository interface doesn't include count operations
- // This would need to be added to the interface or handled differently
- return 0;
- }
- // =========================================================================
- // Permission Operations
- // =========================================================================
- /**
- * Adds a permission to a user.
- *
- * @param user The user to add the permission to
- * @param permission The permission to add
- * @throws Error on storage failure
- */
- public async void add_permission_async(User user, string permission) throws Error {
- yield _repository.add_permission(user.id, permission);
- }
- /**
- * Removes a permission from a user.
- *
- * @param user The user to remove the permission from
- * @param permission The permission to remove
- * @throws Error on storage failure
- */
- public async void remove_permission_async(User user, string permission) throws Error {
- yield _repository.remove_permission(user.id, permission);
- }
- /**
- * Checks if a user has a specific permission.
- *
- * @param user The user to check
- * @param permission The permission to check
- * @return true if the user has the permission
- * @throws Error on storage failure
- */
- public async bool has_permission_async(User user, string permission) throws Error {
- return yield _repository.has_permission(user.id, permission);
- }
- /**
- * Gets all permissions for a user.
- *
- * @param user The user to get permissions for
- * @return A Vector of permission strings
- * @throws Error on storage failure
- */
- public async Vector<string> get_permissions_async(User user) throws Error {
- return yield _repository.get_permissions(user.id);
- }
- // =========================================================================
- // App Data Operations
- // =========================================================================
- /**
- * Sets an app data value for a user.
- *
- * @param user The user to set the app data for
- * @param key The app data key
- * @param value The app data value
- * @throws Error on storage failure
- */
- public async void set_app_data_async(User user, string key, string value) throws Error {
- yield _repository.set_app_data(user.id, key, value);
- }
- /**
- * Gets an app data value for a user.
- *
- * @param user The user to get the app data for
- * @param key The app data key
- * @return The app data value, or null if not found
- * @throws Error on storage failure
- */
- public async string? get_app_data_async(User user, string key) throws Error {
- return yield _repository.get_app_data(user.id, key);
- }
- }
- }
|