Browse Source

refactor(auth): integrate AuthorisationContext and add SQL dependencies

- Replace SessionService/PermissionService with AuthorisationContext/AuthorisationService in authentication components
- Add new UserService methods: list_users, delete_user, set_user_permission, clear_user_permissions, get_user_permissions
- Use simplified expression syntax (expr()) throughout authentication code
- Re-enable examples, tools, website, and demo subdirectories in build
- Add SQL dependencies (invercargill_sql_dep, sqlite_dep, invercargill_sql_inversion_dep) to all executable targets
- Include authentication component files in src build
Billy Barrow 1 month ago
parent
commit
e17f6a4ad7

+ 1 - 1
demo/meson.build

@@ -35,6 +35,6 @@ m_dep = meson.get_compiler('c').find_library('m', required: false)
 executable('spry-demo',
     website_sources,
     docs_css_resource,
-    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep, m_dep],
+    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep, m_dep,invercargill_sql_dep, sqlite_dep, invercargill_sql_inversion_dep],
     install: true
 )

+ 6 - 6
examples/meson.build

@@ -1,35 +1,35 @@
 # CounterComponent Example - demonstrates Spry.Component with outlets for counter page
 executable('counter-component',
     'CounterComponent.vala',
-    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep],
+    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep, invercargill_sql_dep, sqlite_dep, invercargill_sql_inversion_dep],
     install: false
 )
 
 # TodoComponent Example - demonstrates Spry.Component for a complete todo list CRUD
 executable('todo-component',
     'TodoComponent.vala',
-    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep],
+    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep, invercargill_sql_dep, sqlite_dep, invercargill_sql_inversion_dep],
     install: false
 )
 
 # SimpleExample - demonstrates basic Spry.Component usage with outlets
 executable('simple-example',
     'SimpleExample.vala',
-    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep],
+    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep, invercargill_sql_dep, sqlite_dep, invercargill_sql_inversion_dep],
     install: false
 )
 
 # TemplateExample - demonstrates PageComponent and PageTemplate for template-based pages
 executable('template-example',
     'TemplateExample.vala',
-    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep],
+    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep, invercargill_sql_dep, sqlite_dep, invercargill_sql_inversion_dep],
     install: false
 )
 
 # ProgressExample - demonstrates continuation feature for real-time progress updates via SSE
 executable('progress-example',
     'ProgressExample.vala',
-    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep],
+    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep, invercargill_sql_dep, sqlite_dep, invercargill_sql_inversion_dep],
     install: false
 )
 
@@ -37,7 +37,7 @@ executable('progress-example',
 # Includes: user registration, authentication, permissions, protected pages
 executable('users-example',
     'UsersExample.vala',
-    dependencies: [spry_dep, spry_authentication_dep, spry_authorisation_dep, astralis_dep, invercargill_dep, inversion_dep, invercargill_sql_dep, sqlite_dep],
+    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep, invercargill_sql_dep, sqlite_dep, invercargill_sql_inversion_dep],
     install: false
 )
 

+ 4 - 4
meson.build

@@ -25,7 +25,7 @@ sodium_deps = declare_dependency(sources: sodium_vapi, dependencies: sodium_c_li
 add_project_arguments(['--vapidir', vapi_dir], language: 'vala')
 
 subdir('src')
-# subdir('examples')
-# subdir('tools')
-# subdir('website')
-# subdir('demo')
+subdir('examples')
+subdir('tools')
+subdir('website')
+subdir('demo')

+ 17 - 53
src/Authentication/Components/LoginFormComponent.vala

@@ -1,4 +1,5 @@
 using Spry;
+using Spry.Authorisation;
 using Inversion;
 using Astralis;
 using Invercargill.DataStructures;
@@ -12,7 +13,7 @@ namespace Spry.Authentication.Components {
      * - Displays login form with username/email and password fields
      * - Handles form submission via HTMX
      * - Authenticates users via UserService
-     * - Creates sessions and sets cookies via SessionService
+     * - Sets authorisation cookies via AuthorisationService
      * - Displays error messages on failed authentication
      * - Redirects to configurable URL on success
      *
@@ -34,8 +35,8 @@ namespace Spry.Authentication.Components {
     public class LoginFormComponent : Component {
 
         private UserService _user_service = inject<UserService>();
-        private SessionService _session_service = inject<SessionService>();
-        private PermissionService? _permission_service = inject<PermissionService>();
+        private AuthorisationContext _auth_context = inject<AuthorisationContext>();
+        private AuthorisationService _auth_service = inject<AuthorisationService>();
         private HttpContext _http_context = inject<HttpContext>();
 
         // =========================================================================
@@ -174,12 +175,12 @@ namespace Spry.Authentication.Components {
                 return;
             }
 
-            // Attempt authentication
-            stdout.printf("LOGIN DEBUG: Calling authenticate_async()...\n");
-            var user = yield _user_service.authenticate_async(username, password);
-            stdout.printf("LOGIN DEBUG: authenticate_async() returned user: %s\n", user != null ? user.id : "null");
+            // Attempt authentication using the refined authentication system
+            stdout.printf("LOGIN DEBUG: Calling authenticate_user()...\n");
+            var token = yield _user_service.authenticate_user(username, password);
+            stdout.printf("LOGIN DEBUG: authenticate_user() returned token: %s\n", token != null ? "valid" : "null");
 
-            if (user == null) {
+            if (token == null) {
                 // Authentication failed - show generic error message
                 // (Don't reveal whether username or password was wrong for security)
                 stdout.printf("LOGIN DEBUG: Authentication failed - invalid credentials\n");
@@ -187,45 +188,15 @@ namespace Spry.Authentication.Components {
                 return;
             }
 
-            stdout.printf("LOGIN DEBUG: Authentication successful for user: %s (username: %s, email: %s)\n",
-                user.id, user.username, user.email);
-
-            // Get client info for session tracking
-            var ip_address = _http_context.request.remote_address;
-            var user_agent = _http_context.request.headers.get_any_or_default("User-Agent");
-            stdout.printf("LOGIN DEBUG: Client IP: %s, User-Agent: %s\n",
-                ip_address ?? "null", user_agent ?? "null");
-
-            // Create session
-            stdout.printf("LOGIN DEBUG: Creating session...\n");
-            Session? session;
-            if (remember_me) {
-                // Create session with extended duration for "remember me"
-                // Note: Current SessionService uses configured duration;
-                // for extended sessions, we'd need to modify SessionService
-                // For now, create a regular session
-                session = yield _session_service.create_session_async(user.id, ip_address, user_agent);
-            } else {
-                session = yield _session_service.create_session_async(user.id, ip_address, user_agent);
-            }
-            stdout.printf("LOGIN DEBUG: Session creation result: %s\n", session != null ? session.id : "null");
-
-            if (session == null) {
-                stdout.printf("LOGIN DEBUG: ERROR - Failed to create session\n");
-                error_message = "Failed to create session. Please try again.";
-                return;
-            }
+            stdout.printf("LOGIN DEBUG: Authentication successful for user: %s\n",
+                token.user_identifier != null ? token.user_identifier.to_string() : "unknown");
 
-            // Generate session token
-            stdout.printf("LOGIN DEBUG: Generating session token...\n");
-            var token = _session_service.generate_session_token(session);
-            stdout.printf("LOGIN DEBUG: Token generated (length: %d)\n", token.length);
-
-            // Set session cookie using ResponseState
-            // This accumulates the cookie header to be applied when to_result() is called
-            stdout.printf("LOGIN DEBUG: Setting session cookie via ResponseState...\n");
-            response.set_cookie("spry_session", token, 86400, "/", true, true, "Strict");
-            stdout.printf("LOGIN DEBUG: Session cookie set\n");
+            // Set authorisation cookie using the refined AuthorisationService
+            // Note: We use response.add_header to set the Set-Cookie header directly
+            stdout.printf("LOGIN DEBUG: Setting authorisation cookie...\n");
+            var token_str = token.cryptographic_token.to_base64();
+            response.add_header("Set-Cookie", @"_spry-authorisation=$token_str; Secure");
+            stdout.printf("LOGIN DEBUG: Authorisation cookie set\n");
 
             // Set up HTMX redirect using ResponseState
             // This sets the HX-Redirect header for client-side redirect
@@ -234,13 +205,6 @@ namespace Spry.Authentication.Components {
             login_successful = true;
             error_message = null;
             stdout.printf("LOGIN DEBUG: Login successful! Redirect to: %s\n", redirect_url);
-
-            // Optional: Check permissions if permission_service is available
-            // This can be used for post-login permission checks
-            if (_permission_service != null) {
-                // Subclasses can override to add permission checks
-                // e.g., require certain permissions to access specific areas
-            }
         }
 
         // =========================================================================

+ 23 - 11
src/Authentication/Components/NewUserComponent.vala

@@ -1,4 +1,5 @@
 using Spry;
+using Spry.Authorisation;
 using Inversion;
 using Astralis;
 using Invercargill;
@@ -28,7 +29,7 @@ namespace Spry.Authentication.Components {
 
         private Vector<string> _selected_permissions;
         private UserService _user_service = inject<UserService>();
-        private PermissionService _permission_service = inject<PermissionService>();
+        private AuthorisationContext _auth_context = inject<AuthorisationContext>();
         private ComponentFactory _factory = inject<ComponentFactory>();
         private HttpContext _http_context = inject<HttpContext>();
 
@@ -247,13 +248,23 @@ namespace Spry.Authentication.Components {
             }
 
             try {
-                // Create the user
-                var user = yield _user_service.create_user_async(username, email, password);
+                // Create the user using the new register_user method
+                // Using empty strings for forename/surname and current date for date_of_birth
+                // as these fields are not collected in this form
+                var user = yield _user_service.register_user(
+                    username,
+                    email,
+                    "",  // forename - not collected in this form
+                    "",  // surname - not collected in this form
+                    new DateTime.now_utc(),  // date_of_birth - using current date as placeholder
+                    password,
+                    true  // enabled - new users are enabled by default
+                );
 
                 // Set permissions if any were selected
                 if (_selected_permissions.length > 0) {
                     foreach (var perm in _selected_permissions) {
-                        yield _permission_service.set_permission_async(user, perm);
+                        yield _user_service.set_user_permission(user.id, perm);
                     }
                 }
 
@@ -261,14 +272,15 @@ namespace Spry.Authentication.Components {
                 // This ensures the user list is refreshed without needing parent references
                 set_refresh_response();
 
-            } catch (UserError.DUPLICATE_USERNAME e) {
-                error_message = "Username already exists";
-            } catch (UserError.DUPLICATE_EMAIL e) {
-                error_message = "Email already registered";
-            } catch (UserError e) {
-                error_message = e.message;
             } catch (Error e) {
-                error_message = "Error: %s".printf(e.message);
+                // Handle database constraint violations for duplicate username/email
+                if (e.message.contains("username") && e.message.down().contains("unique")) {
+                    error_message = "Username already exists";
+                } else if (e.message.contains("email") && e.message.down().contains("unique")) {
+                    error_message = "Email already registered";
+                } else {
+                    error_message = "Error: %s".printf(e.message);
+                }
             }
         }
 

+ 71 - 32
src/Authentication/Components/UserDetailsComponent.vala

@@ -3,6 +3,7 @@ using Inversion;
 using Astralis;
 using Invercargill;
 using Invercargill.DataStructures;
+using Spry.Authorisation;
 
 namespace Spry.Authentication.Components {
 
@@ -29,10 +30,9 @@ namespace Spry.Authentication.Components {
      */
     public class UserDetailsComponent : Component {
 
-        private User _user;
-        private PermissionService _permission_service = inject<PermissionService>();
+        private UserProjection _user;
+        private AuthorisationContext _auth_context = inject<AuthorisationContext>();
         private UserService _user_service = inject<UserService>();
-        private SessionService _session_service = inject<SessionService>();
         private ComponentFactory _factory = inject<ComponentFactory>();
         private HttpContext _http_context = inject<HttpContext>();
 
@@ -57,7 +57,7 @@ namespace Spry.Authentication.Components {
         // State Properties (must be public for template expression access)
         // =========================================================================
 
-        public string user_id { get; private set; default = ""; }
+        public int64 user_id { get; private set; default = 0; }
         public string username { get; private set; default = ""; }
         public string email { get; private set; default = ""; }
         public string created_at { get; private set; default = ""; }
@@ -75,13 +75,23 @@ namespace Spry.Authentication.Components {
          *
          * @param user The user to display
          */
-        public void set_user(User user) {
+        public void set_user(UserProjection user) {
             _user = user;
             user_id = user.id;
             username = user.username;
             email = user.email;
-            created_at = user.created_at.format("%Y-%m-%d %H:%M:%S UTC");
-            permissions = user.permissions;
+            created_at = user.created.format("%Y-%m-%d %H:%M:%S UTC");
+            // Convert ImmutableLot<string> to string[]
+            var perm_list = new List<string>();
+            foreach (var perm in user.permissions) {
+                perm_list.append(perm);
+            }
+            var perm_array = new string[perm_list.length()];
+            int i = 0;
+            foreach (var perm in perm_list) {
+                perm_array[i++] = perm;
+            }
+            permissions = perm_array;
             permission_count = permissions.length;
         }
 
@@ -90,7 +100,7 @@ namespace Spry.Authentication.Components {
          *
          * @return The user being displayed
          */
-        public User get_user() {
+        public UserProjection get_user() {
             return _user;
         }
 
@@ -122,7 +132,7 @@ namespace Spry.Authentication.Components {
                         <tbody>
                             <tr>
                                 <td style="padding: 0.5rem 0; font-weight: 500; width: 150px; vertical-align: top;">User ID</td>
-                                <td style="padding: 0.5rem 0;"><code content-expr="this.user_id" style="font-size: 0.85rem; background: #f5f5f5; padding: 0.125rem 0.25rem; border-radius: 3px;"></code></td>
+                                <td style="padding: 0.5rem 0;"><code content-expr="this.user_id.to_string()" style="font-size: 0.85rem; background: #f5f5f5; padding: 0.125rem 0.25rem; border-radius: 3px;"></code></td>
                             </tr>
                             <tr>
                                 <td style="padding: 0.5rem 0; font-weight: 500;">Username</td>
@@ -174,7 +184,7 @@ namespace Spry.Authentication.Components {
                             <tbody>
                                 <tr>
                                     <td style="padding: 0.5rem 0; font-weight: 500; width: 150px;">User ID</td>
-                                    <td style="padding: 0.5rem 0;"><code content-expr="this.user_id" style="font-size: 0.85rem; background: #f5f5f5; padding: 0.125rem 0.25rem; border-radius: 3px;"></code></td>
+                                    <td style="padding: 0.5rem 0;"><code content-expr="this.user_id.to_string()" style="font-size: 0.85rem; background: #f5f5f5; padding: 0.125rem 0.25rem; border-radius: 3px;"></code></td>
                                 </tr>
                                 <tr>
                                     <td style="padding: 0.5rem 0; font-weight: 500;">Username *</td>
@@ -373,26 +383,45 @@ namespace Spry.Authentication.Components {
             }
 
             try {
-                // Update user object
-                _user.set_username(new_username);
-                _user.email = new_email;
-                
+                // Update user using alter_user
+                yield _user_service.alter_user(
+                    _user.id,
+                    new_username,
+                    new_email,
+                    _user.forename ?? "",
+                    _user.surname ?? "",
+                    _user.date_of_birth,
+                    _user.enabled
+                );
+
                 // Update password if provided
                 if (new_password.length > 0) {
-                    yield _user_service.set_password_async(_user, new_password);
+                    yield _user_service.set_password(_user.id, new_password);
                 }
-                
-                // Persist user changes
-                yield _user_service.update_user_async(_user);
 
                 // Update permissions - clear all and re-add
-                yield _permission_service.clear_all_permissions_async(_user);
+                yield _user_service.clear_user_permissions(_user.id);
                 foreach (var perm in _editing_permissions) {
-                    yield _permission_service.set_permission_async(_user, perm);
+                    yield _user_service.set_user_permission(_user.id, perm);
                 }
 
-                // Update local state
-                set_user(_user);
+                // Fetch updated user projection to refresh local state
+                var updated_permissions = yield _user_service.get_user_permissions(_user.id);
+                // Update local state with new values
+                username = new_username;
+                email = new_email;
+                var perm_list = new List<string>();
+                foreach (var perm in updated_permissions) {
+                    perm_list.append(perm);
+                }
+                var perm_array = new string[perm_list.length()];
+                int i = 0;
+                foreach (var perm in perm_list) {
+                    perm_array[i++] = perm;
+                }
+                permissions = perm_array;
+                permission_count = permissions.length;
+
                 is_editing = false;
                 error_message = null;
                 _editing_permissions = null;
@@ -412,20 +441,30 @@ namespace Spry.Authentication.Components {
         }
 
         private async void handle_delete_user_async() throws Error {
-            // Prevent self-deletion
-            var auth_result = yield _session_service.authenticate_request_async(_http_context, _user_service);
-            if (auth_result.is_authenticated && auth_result.user != null) {
-                if (auth_result.user.id == user_id) {
-                    // Can't delete self - show error
-                    error_message = "Cannot delete your own account";
-                    yield prepare();
-                    return;
-                }
+            // Check authorization using AuthorisationContext
+            if (_auth_context.is_anonymous()) {
+                error_message = "You must be logged in to delete users";
+                yield prepare();
+                return;
+            }
+
+            if (!_auth_context.has_permission("user-management")) {
+                error_message = "You do not have permission to delete users";
+                yield prepare();
+                return;
+            }
+
+            // Prevent self-deletion - compare int64 user ids
+            int64? current_user_id = _auth_context.token.user_identifier.as_int64_or_null();
+            if (current_user_id != null && current_user_id == user_id) {
+                error_message = "Cannot delete your own account";
+                yield prepare();
+                return;
             }
 
             // Delete the user
             try {
-                yield _user_service.delete_user_async(user_id);
+                yield _user_service.delete_user(user_id);
 
                 // Set hx-refresh header to cause page reload
                 // This ensures the user list is refreshed without needing parent references

+ 7 - 17
src/Authentication/Components/UserManagementComponent.vala

@@ -3,6 +3,7 @@ using Inversion;
 using Astralis;
 using Invercargill;
 using Invercargill.DataStructures;
+using Spry.Authorisation;
 
 namespace Spry.Authentication.Components {
 
@@ -30,9 +31,8 @@ namespace Spry.Authentication.Components {
      */
     public class UserManagementComponent : Component {
 
-        private PermissionService _permission_service = inject<PermissionService>();
+        private AuthorisationContext _auth_context = inject<AuthorisationContext>();
         private UserService _user_service = inject<UserService>();
-        private SessionService _session_service = inject<SessionService>();
         private ComponentFactory _factory = inject<ComponentFactory>();
         private HttpContext _http_context = inject<HttpContext>();
 
@@ -55,7 +55,7 @@ namespace Spry.Authentication.Components {
         public string? error_message { get; private set; default = null; }
         public bool access_denied { get; private set; default = false; }
         public bool show_create_form { get; private set; default = false; }
-        public Vector<User> users { get; private set; }
+        public ImmutableLot<UserProjection> users { get; private set; }
 
         // =========================================================================
         // Component Implementation
@@ -114,23 +114,13 @@ namespace Spry.Authentication.Components {
         }}
 
         public async override void prepare() throws Error {
-            // Initialize users vector
-            users = new Vector<User>();
-
-            // Check permission
-            var auth_result = yield _session_service.authenticate_request_async(_http_context, _user_service);
-
-            if (!auth_result.is_authenticated || auth_result.user == null) {
+            // Check authorization using AuthorisationContext
+            if (_auth_context.is_anonymous()) {
                 access_denied = true;
                 return;
             }
 
-            var has_permission = yield _permission_service.has_permission_by_id_async(
-                auth_result.user.id,
-                PermissionService.USER_MANAGEMENT
-            );
-
-            if (!has_permission) {
+            if (!_auth_context.has_permission("user-management")) {
                 access_denied = true;
                 return;
             }
@@ -217,7 +207,7 @@ namespace Spry.Authentication.Components {
         // =========================================================================
 
         private async void load_users_async() throws Error {
-            users = yield _user_service.list_users_async(0, 100);
+            users = yield _user_service.list_users(0, 100);
         }
     }
 }

+ 2 - 2
src/Authentication/UserIdentityProvider.vala

@@ -12,12 +12,12 @@ namespace Spry.Authentication {
 
         public async Authorisation.Identity? get_identity_by_identifier (Element id) throws Error {
             return yield db.query<UserProjection>()
-                .where_expr(ExpressionParser.parse_with_params("id == $0", id))
+                .where_expr(expr("id == $0", id))
                 .first_async();
         }
         public async Authorisation.Identity? get_identity_by_username (string username) throws Error {
             return yield db.query<UserProjection>()
-                .where_expr(ExpressionParser.parse_with_params("username == $0", new NativeElement<string>(username)))
+                .where_expr(expr("username == $0", new NativeElement<string>(username)))
                 .first_async();
         }
         

+ 49 - 4
src/Authentication/UserService.vala

@@ -2,6 +2,7 @@ using InvercargillSql.Orm;
 using Spry.Authorisation;
 using Invercargill.Expressions;
 using Invercargill;
+using Invercargill.DataStructures;
 using Inversion;
 
 namespace Spry.Authentication {
@@ -14,7 +15,7 @@ namespace Spry.Authentication {
 
         public async AuthorisationToken? authenticate_user(string username, string password) throws Error {
             var user = yield db.query<UserProjection>()
-                .where_expr(ExpressionParser.parse_with_params("username == $0", new NativeElement<string>(username)))
+                .where_expr(expr("username == $0", new NativeElement<string>(username)))
                 .first_async();
 
             if(!Sodium.PasswordHashing.check(user.password_hash, password)){
@@ -43,7 +44,7 @@ namespace Spry.Authentication {
 
         public async void set_password(int64 user_id, string password) throws Error {
             var user = yield db.query<UserEntity>()
-                .where_expr(ExpressionParser.parse_with_params("id == $0", new NativeElement<int64?>(user_id)))
+                .where_expr(expr("id == $0", new NativeElement<int64?>(user_id)))
                 .first_async();
 
             user.password_hash = Sodium.PasswordHashing.hash(password);
@@ -53,7 +54,7 @@ namespace Spry.Authentication {
 
         public async UserEntity alter_user(int64 user_id, string username, string email, string forename, string surname, DateTime date_of_birth, bool enabled) throws Error {
             var user = yield db.query<UserEntity>()
-                .where_expr(ExpressionParser.parse_with_params("id == $0", new NativeElement<int64?>(user_id)))
+                .where_expr(expr("id == $0", new NativeElement<int64?>(user_id)))
                 .first_async();
 
             user.username = username;
@@ -70,7 +71,7 @@ namespace Spry.Authentication {
 
         public async void set_user_enabled(int64 user_id, bool enabled) throws Error {
             var user = yield db.query<UserEntity>()
-                .where_expr(ExpressionParser.parse_with_params("id == $0", new NativeElement<int64?>(user_id)))
+                .where_expr(expr("id == $0", new NativeElement<int64?>(user_id)))
                 .first_async();
 
             user.modified = new DateTime.now_utc();
@@ -79,6 +80,50 @@ namespace Spry.Authentication {
             db.update<UserEntity>(user);
         }
 
+        public async ImmutableLot<UserProjection> list_users(int64 offset = 0, int64 limit = 100) throws Error {
+            return yield db.query<UserProjection>()
+                .offset(offset)
+                .limit(limit)
+                .materialise_async();
+        }
+
+        public async void delete_user(int64 user_id) throws Error {
+            var user = yield db.query<UserEntity>()
+                .where_expr(expr("id == $0", new NativeElement<int64?>(user_id)))
+                .first_async();
+            db.delete<UserEntity>(user);
+        }
+
+        public async void set_user_permission(int64 user_id, string permission) throws Error {
+            var user_permission = new UserPermissionEntity() {
+                user_id = user_id,
+                permission = permission
+            };
+            db.insert<UserPermissionEntity>(user_permission);
+        }
+
+        public async void clear_user_permissions(int64 user_id) throws Error {
+            var permissions = yield db.query<UserPermissionEntity>()
+                .where_expr(expr("user_id == $0", new NativeElement<int64?>(user_id)))
+                .materialise_async();
+
+            foreach (var permission in permissions) {
+                db.delete<UserPermissionEntity>(permission);
+            }
+        }
+
+        public async ImmutableLot<string> get_user_permissions(int64 user_id) throws Error {
+            var permissions = yield db.query<UserPermissionEntity>()
+                .where_expr(expr("user_id == $0", new NativeElement<int64?>(user_id)))
+                .materialise_async();
+
+            var result = new Vector<string>();
+            foreach (var permission in permissions) {
+                result.add(permission.permission);
+            }
+            return result.to_immutable_buffer();
+        }
+
     }
 
 }

+ 4 - 1
src/meson.build

@@ -34,7 +34,10 @@ sources = files(
     'Authentication/UserService.vala',
     'Authentication/UserIdentityProvider.vala',
     'Authentication/AuthenticationModule.vala',
-    'Authentication/UserTableMigration.vala',
+    'Authentication/Components/LoginFormComponent.vala',
+    'Authentication/Components/NewUserComponent.vala',
+    'Authentication/Components/UserDetailsComponent.vala',
+    'Authentication/Components/UserManagementComponent.vala',
 )
 
 

+ 1 - 1
tools/spry-mkssr/meson.build

@@ -4,6 +4,6 @@ spry_mkssr_sources = files(
 
 spry_mkssr = executable('spry-mkssr',
     spry_mkssr_sources,
-    dependencies: [glib_dep, gobject_dep, gio_dep, invercargill_dep, json_glib_dep, astralis_dep, inversion_dep, libxml_dep],
+    dependencies: [glib_dep, gobject_dep, gio_dep, invercargill_dep, json_glib_dep, astralis_dep, inversion_dep, libxml_dep, invercargill_sql_dep, sqlite_dep, invercargill_sql_inversion_dep],
     install: true
 )

+ 1 - 1
website/meson.build

@@ -24,6 +24,6 @@ m_dep = meson.get_compiler('c').find_library('m', required: false)
 
 executable('spry-website',
     website_sources,
-    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep, m_dep],
+    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep, m_dep, invercargill_sql_dep, sqlite_dep, invercargill_sql_inversion_dep],
     install: true
 )