1. Identiteit en autorisatie
Deze sectie bevat de kern van accountidentiteit, profielgegevens, gebruikersinstellingen, roltoekenning en permission-based RBAC. Rollen blijven bestaan als bundels, maar autorisatiechecks lopen tegen permissions. De effectieve permissions van een gebruiker worden afgeleid uit UserRoles, Roles, RolePermissions en Permissions. Relatie-, niveau-, kind-, run- en supportcontext blijven aanvullende domeincontroles.
1.1 Users
| Tabelnaam | Categorie | Doel / verantwoordelijkheid | Gerelateerde tabellen |
|---|---|---|---|
| Users | Identiteit | Basisaccount van een gebruiker, inclusief interne identiteit, koppeling naar de externe identity provider en weergave-gerelateerde gegevens. | Roles, UserRoles, Permissions, RolePermissions, ProfileAvatars, UserSettings, RelationshipInvitations, UserRelationships, SystemMessages, PrivateMessages |
| Veldnaam | Type | Default | PK | FK | Verwijst naar | Unique | Nullable | Index | Opmerking |
|---|---|---|---|---|---|---|---|---|---|
| Id | uniqueidentifier | - | J | N | - | J | N | J | Primaire sleutel van de gebruiker. GUID wordt in de applicatiecode gegenereerd; geen database-default. |
| nvarchar(320) | - | N | N | - | J | N | J | Authenticatie-e-mailadres voor login en uitnodigingen. Bij anonimisering wordt dit vervangen door een systeemmatig gegenereerd anoniem adres. | |
| DisplayName | nvarchar(150) | - | N | N | - | N | N | J | Weergavenaam in de interface. |
| MiddleName | nvarchar(50) | null | N | N | - | N | J | N | Optioneel tussenvoegsel voor persoonlijke aanspreking en correcte naamweergave. |
| LastName | nvarchar(100) | null | N | N | - | N | J | N | Achternaam voor persoonlijke aanspreking en correcte naamweergave. |
| FirstName | nvarchar(100) | - | N | N | - | N | J | N | Voornaam voor persoonlijke aanspreking. |
| ProfileAvatarId | uniqueidentifier | null | N | J | ProfileAvatars.Id | N | J | N | Verwijzing naar een vooraf gedefinieerde profielafbeelding. |
| IsActive | bit | 1 | N | N | - | N | N | J | Soft delete / actieve status van account. |
| LastSeenAtUtc | datetime2 | null | N | N | - | N | J | J | Laatste bekende activiteit of login. De first-visit-greeting wordt bij sessieopbouw vóór het bijwerken van deze waarde afgeleid en tijdelijk in de auth-sessie opgeslagen. |
| OnboardingCompletedAtUtc | datetimeoffset | null | N | N | - | N | J | J | Tijdstip waarop de centrale OefenHub-onboarding voor dit account volledig is afgerond. Leeg betekent dat de onboarding-gate nog verplichte stappen moet beoordelen. |
| CreatedAtUtc | datetime2 | sysutcdatetime() | N | N | - | N | N | J | Aanmaakdatum van het account. |
| UpdatedAtUtc | datetime2 | sysutcdatetime() | N | N | - | N | N | N | Laatst bijgewerkt op. |
| ExternalId | nvarchar(100) | - | N | N | - | J | N | J | Stabiele externe identity provider-id, bijvoorbeeld de Keycloak user-id. Wordt server-side gevuld en is niet via de GUI wijzigbaar. |
Validaties / constraints
- E-mailadres is uniek en case-insensitive te behandelen.
- ExternalId is uniek en vormt de stabiele koppelsleutel naar de externe identity provider. De interne GUID in Users.Id blijft de primaire sleutel van OefenHub.
- De waarde # is gereserveerd als systeemwaarde voor geanonimiseerde accounts en mag niet via de normale UI als tussenvoegsel worden opgeslagen.
- DisplayName is verplicht voor uniforme weergave in UI en berichten.
- Profielafbeelding verwijst alleen naar een vooraf gedefinieerde avatar.
Business rules
- Een gebruiker kan meerdere rollen hebben, behalve combinaties met de rol Leerling. Rollen zijn bundels van permissions; directe rolchecks zijn geen autorisatiebeslissing meer.
- Users bevat profielgegevens, maar geen voorkeuren of toegankelijkheidsinstellingen; die zitten in UserSettings.
- Authenticatie, wachtwoorden, sessies en credential-lifecycle liggen buiten OefenHub bij de identity provider. Users bewaart daarom een interne identiteit plus een stabiele externe koppelsleutel, maar is niet de bronhouder van credentials.
- Een functionele accountverwijdering in OefenHub betekent geen hard delete van het domeinrecord, maar een gecontroleerde anonimiseer- en cleanupflow met behoud van historie waar dat functioneel nodig blijft.
Lifecycle / gedrag
- Accounts worden niet hard verwijderd. IsActive = false markeert een account als gedeactiveerd, terwijl historie behouden blijft.
- Bij anonimisering worden naam en e-mailadres systeemmatig overschreven. Voornaam wordt Anoniem, tussenvoegsel wordt #, achternaam wordt een korte niet-voorspelbare unieke code en e-mailadres wordt vervangen door een systeemadres in de vorm anoniem.<code>@verwijderd.acc.
- Open domeindata zoals relaties, uitnodigingen en toegangscontexten worden tijdens de accountverwijderflow administratief opgeschoond; niet-afgeronde oefenruns blijven historisch bestaan maar worden niet meer hervatbaar.
Designkeuzes
- Gebruikeridentiteit is bewust gescheiden van rollen, relaties en gebruikersinstellingen om multi-role gedrag en uitbreidbare voorkeuren mogelijk te maken.
- De combinatie van een interne GUID en een aparte ExternalId voorkomt dat de tabelstructuur direct afhankelijk wordt van één specifieke identity provider en houdt toekomstige providerwissels of migraties beter beheersbaar.
- De zeer herleidbare lifecycle van accountaanmaak, verwijdering en anonimisering wordt niet alleen via relationele historie ondersteund, maar aanvullend via een apart applicatielogbestand voor accountacties.
Foreign keys op databaseniveau
- Harde foreign keys op databaseniveau: ProfileAvatarId -> ProfileAvatars.Id.
Functionele / logische verwijzingen zonder harde FK
- De velden Email bevatten inhoudelijke of technische contextwaarden en zijn daarom logische samenhang zonder harde foreign key.
FK + snapshot
- FK + snapshot: niet van toepassing binnen deze tabel.
1.2 Roles
| Tabelnaam | Categorie | Doel / verantwoordelijkheid | Gerelateerde tabellen |
|---|---|---|---|
| Roles | Autorisatie | Definitie van beschikbare rollen binnen OefenHub. Rollen groeperen permissions via RolePermissions. | UserRoles, RolePermissions, UserRelationships, RelationshipInvitations |
| Veldnaam | Type | Default | PK | FK | Verwijst naar | Unique | Nullable | Index | Opmerking |
|---|---|---|---|---|---|---|---|---|---|
| Id | uniqueidentifier | - | J | N | - | J | N | J | Primaire sleutel van de rol. GUID wordt in de applicatiecode gegenereerd; geen database-default. |
| Code | nvarchar(50) | - | N | N | - | J | N | J | Technische code, bijvoorbeeld Student, Teacher, Guardian, Admin. |
| Name | nvarchar(100) | - | N | N | - | J | N | N | Leesbare naam van de rol. |
| IsActive | bit | 1 | N | N | - | N | N | J | Maakt rol selecteerbaar of verborgen. |
| IsPublic | bit | 0 | N | N | - | N | N | J | Bepaalt of deze rol door een gebruiker zelf gekozen mag worden bij publieke registratie of profieluitbreiding. Rollen zoals Admin en TestDocent hebben IsPublic = 0 en worden alleen via beheer toegekend. |
| CreatedAtUtc | datetime2 | sysutcdatetime() | N | N | - | N | N | N | Aanmaakdatum. |
Validaties / constraints
- Code en Name zijn uniek.
- Rollen worden centraal beheerd en zijn beperkt tot de in het systeem toegestane waarden.
- Niet-publieke rollen mogen niet zelfstandig door eindgebruikers gekozen worden.
- Rollen geven geen directe autorisatie; effectieve rechten ontstaan via actieve RolePermissions naar actieve Permissions.
Business rules
- Publieke rollen zoals Leerling, Docent en Ouder/Voogd kunnen door een nieuwe gebruiker zelf gekozen worden.
- Rollen zoals Beheerder en TestDocent vereisen expliciete toekenning via een administratieve of beheerlaag.
- Beheer van rollen, permissions en RolePermissions zelf is buiten scope van Feature 11-5 en wordt voorbereid in userstory 17-091 met een aparte Admin-/autorisatiebeheerrol.
Lifecycle / gedrag
- Rollen worden in de praktijk zelden verwijderd; deactiveren geniet de voorkeur boven delete.
- IsPublic bepaalt of een actieve rol in publieke registratie- en keuzeprocessen zichtbaar is.
Designkeuzes
- Rollen in aparte tabel voorkomt hardcoding in meerdere domeinen en maakt rolcontext expliciet in relaties.
- De extra vlag IsPublic scheidt bewust vrij kiesbare rollen van beheer- of testrollen.
- De autorisatielaag zit niet meer in rollen zelf, maar in Permissions en RolePermissions zodat rollen als beheerbare bundels kunnen functioneren.
Foreign keys op databaseniveau
- Geen harde foreign keys op databaseniveau binnen deze tabel.
Functionele / logische verwijzingen zonder harde FK
- De velden Code functioneren als code-, enum- of sleutelwaarden en verwijzen bewust niet via een harde foreign key.
FK + snapshot
- FK + snapshot: niet van toepassing binnen deze tabel.
1.3 UserRoles
| Tabelnaam | Categorie | Doel / verantwoordelijkheid | Gerelateerde tabellen |
|---|---|---|---|
| UserRoles | Autorisatie | Koppelt één of meerdere rollen aan een gebruiker, inclusief auditinformatie over toekenning en intrekking. | Users, Roles, Permissions, RolePermissions |
| Veldnaam | Type | Default | PK | FK | Verwijst naar | Unique | Nullable | Index | Opmerking |
|---|---|---|---|---|---|---|---|---|---|
| Id | uniqueidentifier | - | J | N | - | J | N | J | Primaire sleutel. GUID wordt in de applicatiecode gegenereerd; geen database-default. |
| UserId | uniqueidentifier | - | N | N | Users.Id | N | N | J | Soft link naar Users.Id; geen harde database-FK vanwege de scheiding tussen het authorization-domein en het identity-domein. |
| RoleId | uniqueidentifier | - | N | J | Roles.Id | N | N | J | Toegekende rol. |
| IsActive | bit | 1 | N | N | - | N | N | J | Geeft aan of roltoekenning actief is. |
| GrantedAtUtc | datetime2 | sysutcdatetime() | N | N | - | N | N | N | Moment van toekenning. |
| GrantedByUserId | uniqueidentifier | null | N | N | Users.Id | N | J | N | Soft link naar Users.Id voor de actor die de rol toekende; geen harde database-FK. |
| RevokedAtUtc | datetime2 | null | N | N | - | N | J | N | Moment van intrekking. |
| RevokedByUserId | uniqueidentifier | null | N | N | Users.Id | N | J | N | Soft link naar Users.Id voor de actor die de rol introk; geen harde database-FK. |
Validaties / constraints
- Actieve combinatie UserId + RoleId is uniek.
- Een gebruiker kan meerdere rollen hebben binnen de toegestane combinatieregels.
Business rules
- Roltoekenning bepaalt niet automatisch autorisatie. De effectieve permissions worden via actieve rollen en RolePermissions bepaald en gecachet.
- Iedere wijziging in actieve UserRoles moet de permissioncache voor de gebruiker invalidaten.
Lifecycle / gedrag
- Intrekking van een rol verwijdert historie niet.
- Intrekking of toekenning van een rol leegt de permissioncache voor de gebruiker.
- Eerdere relatie- en berichtdata blijven naar de gebruiker verwijzen.
Designkeuzes
- Aparte koppelingslaag maakt multi-role gebruikers mogelijk met volledige audittrail.
Foreign keys op databaseniveau
- Harde foreign keys op databaseniveau: RoleId -> Roles.Id.
Functionele / logische verwijzingen zonder harde FK
UserId,GrantedByUserIdenRevokedByUserIdzijn soft links naarUsers.Id. Zij worden server-side gevalideerd via het identity-/authorizationcontract, maar krijgen geen harde database-FK omdatUsersin het identity-domein ligt enUserRolesin het authorization-domein. Overige velden zijn inhouds-, status- of auditwaarden.
FK + snapshot
- FK + snapshot: niet van toepassing binnen deze tabel.
1.4 Permissions
| Tabelnaam | Categorie | Doel / verantwoordelijkheid | Gerelateerde tabellen |
|---|---|---|---|
| Permissions | Autorisatie | Definitie van alle expliciete permission-codes die in OefenHub gebruikt mogen worden voor routes, schermen, menu-items, frontpageblokken, services en domeinacties. | Roles, RolePermissions, UserRoles |
| Veldnaam | Type | Default | PK | FK | Verwijst naar | Unique | Nullable | Index | Opmerking |
|---|---|---|---|---|---|---|---|---|---|
| Id | uniqueidentifier | - | J | N | - | J | N | J | Primaire sleutel van de permission. GUID wordt in de applicatiecode gegenereerd; geen database-default. |
| Code | nvarchar(150) | - | N | N | - | J | N | J | Technische permission-code volgens <resource>.<action>[.<scope>], bijvoorbeeld student-results.read.assigned. |
| Name | nvarchar(150) | - | N | N | - | N | N | N | Korte leesbare naam voor beheer-/diagnoseweergave. |
| Description | nvarchar(1000) | - | N | N | - | N | N | N | Engelse toelichting op wat de permission precies toestaat en welke domeinchecks nog nodig zijn. |
| IsActive | bit | 1 | N | N | - | N | N | J | Alleen actieve permissions tellen mee bij effectieve rechten. |
| CreatedAtUtc | datetime2 | sysutcdatetime() | N | N | - | N | N | N | Aanmaakdatum. |
| UpdatedAtUtc | datetime2 | sysutcdatetime() | N | N | - | N | N | N | Laatst bijgewerkt op. |
Validaties / constraints
Codeis uniek en lowercase.Codevolgt de conventie<resource>.<action>[.<scope>].Descriptionis verplicht en beschrijft in het Engels wat de permission doet.- Inactieve permissions worden niet meegenomen in permissioncache of autorisatiechecks.
- Startup-/testvalidatie moet falen wanneer code een permission declareert die niet in deze tabel of seeddata voorkomt.
Business rules
- Permissions zijn het autorisatiecontract voor ingangen en acties.
- Een permission is geen objecttoegang; domeinservices controleren daarna de concrete scope.
- Permissions worden niet per individueel puur visueel element gemaakt, tenzij dat element een zelfstandige security-impact heeft.
- Er komt geen grote centrale C#-permissiecatalogus als tweede bron van waarheid; de runtime bron is deze tabel.
Lifecycle / gedrag
- Permissions worden initieel via migratie/seed beheerd.
- Deactiveren heeft de voorkeur boven verwijderen, omdat RolePermissions en auditcontext historisch herleidbaar moeten blijven.
- Wijzigen of deactiveren van permissions vereist invalidatie van betrokken gebruikerscaches wanneer de wijziging runtime-effect heeft.
Designkeuzes
- Permissions zijn bewust apart van Roles gemodelleerd zodat rollen alleen bundels zijn.
Descriptionis verplicht om beheer, review en audit begrijpelijk te houden.- De permissionnaamgeving is vastgelegd in
docs/ontwerpbronnen/rbac-permissieregister.md.
Foreign keys op databaseniveau
- Geen harde foreign keys op databaseniveau binnen deze tabel.
Functionele / logische verwijzingen zonder harde FK
Codefunctioneert als technische sleutel richting declaratieve metadata in code, maar codebestanden zijn geen database-FK.
FK + snapshot
- FK + snapshot: niet van toepassing binnen deze tabel.
1.5 RolePermissions
| Tabelnaam | Categorie | Doel / verantwoordelijkheid | Gerelateerde tabellen |
|---|---|---|---|
| RolePermissions | Autorisatie | Koppelt permissions aan rollen, inclusief auditinformatie over toekenning en intrekking. | Roles, Permissions, UserRoles |
| Veldnaam | Type | Default | PK | FK | Verwijst naar | Unique | Nullable | Index | Opmerking |
|---|---|---|---|---|---|---|---|---|---|
| Id | uniqueidentifier | - | J | N | - | J | N | J | Primaire sleutel. GUID wordt in de applicatiecode gegenereerd; geen database-default. |
| RoleId | uniqueidentifier | - | N | J | Roles.Id | N | N | J | Rol waaraan de permission gekoppeld is. |
| PermissionId | uniqueidentifier | - | N | J | Permissions.Id | N | N | J | Permission die via de rol wordt toegekend. |
| IsActive | bit | 1 | N | N | - | N | N | J | Alleen actieve koppelingen tellen mee bij effectieve rechten. |
| GrantedAtUtc | datetime2 | sysutcdatetime() | N | N | - | N | N | N | Moment van koppelen. |
| GrantedByUserId | uniqueidentifier | null | N | N | Users.Id | N | J | N | Soft link naar Users.Id voor de actor die de koppeling maakte; geen harde database-FK. |
| RevokedAtUtc | datetime2 | null | N | N | - | N | J | N | Moment van intrekken. |
| RevokedByUserId | uniqueidentifier | null | N | N | Users.Id | N | J | N | Soft link naar Users.Id voor de actor die de koppeling introk; geen harde database-FK. |
Validaties / constraints
- Actieve combinatie
RoleId + PermissionIdis uniek. RoleIdverwijst naar een bestaande rol.PermissionIdverwijst naar een bestaande permission.- Alleen actieve rollen, actieve permissions en actieve RolePermissions tellen mee bij effectieve rechten.
Business rules
- RolePermissions bepalen welke permissions via een rol beschikbaar worden.
- Een gebruiker met meerdere rollen krijgt de distinct union van permissions uit alle actieve rollen.
- Wijzigen van RolePermissions vereist invalidatie van de permissioncache van alle gebruikers met de betrokken rol.
- Beheer van RolePermissions via UI is buiten scope van Feature 11-5 en wordt verder uitgewerkt in Feature 17 story 091.
Lifecycle / gedrag
- Intrekking van een RolePermission verwijdert historie niet.
- Eerdere autorisatiebeslissingen worden niet achteraf herschreven; nieuwe checks gebruiken de actuele cache of herladen bij cache miss/invalidation.
Designkeuzes
- De naam
RolePermissionsvolgt de technische richting rol -> permissie en is explicieter danPermissionRoles. - Auditvelden zijn aanwezig omdat wijzigingen direct security-impact hebben.
Foreign keys op databaseniveau
- Harde foreign keys op databaseniveau: RoleId -> Roles.Id; PermissionId -> Permissions.Id.
Functionele / logische verwijzingen zonder harde FK
GrantedByUserIdenRevokedByUserIdzijn soft links naarUsers.Iden worden applicatief gevalideerd.
FK + snapshot
- FK + snapshot: niet van toepassing binnen deze tabel.
1.6 ProfileAvatars
| Tabelnaam | Categorie | Doel / verantwoordelijkheid | Gerelateerde tabellen |
|---|---|---|---|
| ProfileAvatars | Profiel | Vooraf gedefinieerde profielafbeeldingen waar gebruikers uit kiezen in plaats van vrije uploads. | Users |
| Veldnaam | Type | Default | PK | FK | Verwijst naar | Unique | Nullable | Index | Opmerking |
|---|---|---|---|---|---|---|---|---|---|
| Id | uniqueidentifier | - | J | N | - | J | N | J | Primaire sleutel. GUID wordt in de applicatiecode gegenereerd; geen database-default. |
| Code | nvarchar(50) | - | N | N | - | J | N | J | Technische code of sleutel van de avatar. |
| DisplayName | nvarchar(100) | - | N | N | - | N | N | N | Leesbare naam voor selectie in de UI. |
| AssetPath | nvarchar(500) | - | N | N | - | J | N | N | Pad of referentie naar het vaste avatarbestand. |
| IsActive | bit | 1 | N | N | - | N | N | J | Soft delete / actieve status. |
| CreatedAtUtc | datetime2 | sysutcdatetime() | N | N | - | N | N | N | Aanmaakdatum. |
Validaties / constraints
- Code en AssetPath zijn uniek. Alleen vooraf goedgekeurde avatars zijn toegestaan.
Business rules
- Gebruikers kiezen uit deze vaste set; vrije uploads zijn niet toegestaan.
- Afbeeldingen zijn ontworpen voor compact gebruik binnen de applicatie, bijvoorbeeld 64x64.
Lifecycle / gedrag
- Avatars worden normaal via code en/of migratie toegevoegd en niet door eindgebruikers aangemaakt.
- Deactiveren heeft de voorkeur boven verwijderen.
Designkeuzes
- Aparte avatartabel voorkomt ongewenste afbeeldingen en houdt profielafbeeldingen veilig, beheersbaar en uniform.
Foreign keys op databaseniveau
- Geen harde foreign keys op databaseniveau binnen deze tabel.
Functionele / logische verwijzingen zonder harde FK
- De velden Code functioneren als code-, enum- of sleutelwaarden en verwijzen bewust niet via een harde foreign key. De velden AssetPath bevatten inhoudelijke of technische contextwaarden en zijn daarom logische samenhang zonder harde foreign key.
FK + snapshot
- FK + snapshot: niet van toepassing binnen deze tabel.
1.7 UserSettings
| Tabelnaam | Categorie | Doel / verantwoordelijkheid | Gerelateerde tabellen |
|---|---|---|---|
| UserSettings | Gebruikersinstellingen | Gebruikersspecifieke voorkeuren en toegankelijkheidsinstellingen die buiten authenticatie en basisprofiel vallen. | Users, TeacherLevels, SiteFeatureToggles |
| Veldnaam | Type | Default | PK | FK | Verwijst naar | Unique | Nullable | Index | Opmerking |
|---|---|---|---|---|---|---|---|---|---|
| Id | uniqueidentifier | - | J | N | - | J | N | J | Primaire sleutel. GUID wordt in de applicatiecode gegenereerd; geen database-default. |
| UserId | uniqueidentifier | - | N | J | Users.Id | J | N | J | Één-op-één koppeling naar de gebruiker. |
| SelectedTeacherLevelId | uniqueidentifier | null | N | N | TeacherLevels.Id | N | J | J | Soft link naar TeacherLevels.Id; geen harde database-FK vanwege de scheiding met het catalog-/docentstructuurdomein. |
| DontWarnAgainOnDunno | bit | 0 | N | N | - | N | N | N | Verborgen voorkeur voor de waarschuwing bij "Geen idee". |
| AccessibilityHighContrastEnabled | bit | 0 | N | N | - | N | N | N | Verhoogd contrast binnen OefenHub. |
| AccessibilityUseDyslexiaFont | bit | 0 | N | N | - | N | N | N | Gebruikt het dyslexielettertype binnen OefenHub. |
| AccessibilityFontScalePercent | int | 100 | N | N | - | N | N | N | Standaard lettergrootte in procenten, bijvoorbeeld 90, 100 of 110. |
| TeacherStudentListNameFormat | nvarchar(50) | "Default" | N | N | - | N | N | N | Persoonlijke voorkeur voor naamweergave in docentoverzichten. |
| TeacherStudentListSortOrder | nvarchar(10) | "ASC" | N | N | - | N | N | N | Persoonlijke sorteervoorkeur in docentoverzichten. |
| UpdatedAtUtc | datetime2 | sysutcdatetime() | N | N | - | N | N | N | Laatst bijgewerkt op. |
Validaties / constraints
- UserId is uniek zodat per gebruiker één settingsrecord bestaat. SelectedTeacherLevelId moet null zijn of verwijzen naar een bestaand niveau.
- AccessibilityFontScalePercent gebruikt alleen vooraf toegestane waarden of een afgebakend bereik.
Business rules
- Deze tabel bevat alle niet-profielgebonden gebruikersinstellingen, waaronder toegankelijkheidsopties en verborgen voorkeuren.
- Toegankelijkheidsinstellingen worden in de database opgeslagen en daarnaast gespiegeld naar een cookie zodat ze al vóór inloggen binnen OefenHub toegepast kunnen worden.
- Inkomende en uitgaande cookies zijn afgeleid van deze brondata en vormen niet de primaire waarheid.
Lifecycle / gedrag
- Instellingen worden direct opgeslagen zodra de gebruiker ze wijzigt.
- Wanneer de sitebrede toegankelijkheidsfeature is uitgeschakeld, blijven bestaande gebruikerswaarden bewaard maar worden ze functioneel genegeerd totdat de feature weer is ingeschakeld.
Designkeuzes
- Voorkeuren en toegankelijkheid zitten bewust in dezelfde tabel, omdat zij samen het instelbare niet-profielgebonden gebruikersgedrag vormen.
- Authenticatie en wachtwoordbeheer blijven buiten OefenHub in Keycloak.
Foreign keys op databaseniveau
- Harde foreign keys op databaseniveau: UserId -> Users.Id.
Functionele / logische verwijzingen zonder harde FK
SelectedTeacherLevelIdis een soft link naarTeacherLevels.Id. De gekozen waarde wordt server-side gevalideerd tegen de toegestane niveaucontext van de gebruiker, maar krijgt geen harde database-FK vanwege de modulegrens. De veldenTeacherStudentListNameFormaten vergelijkbare voorkeuren functioneren als code-, enum- of sleutelwaarden en verwijzen bewust niet via een harde foreign key.
FK + snapshot
- FK + snapshot: niet van toepassing binnen deze tabel.
1.8 Relatiecontext binnen identiteit en autorisatie
Deze aanvullende regels zijn afgeleid uit de uitgewerkte relatie-usecases voor bekijken, uitnodigen, accepteren/afwijzen en ontkoppelen.
Registratie en openstaande relatie-uitnodigingen
- Na succesvolle registratie controleert OefenHub of er openstaande
RelationshipInvitationsbestaan met hetzelfde genormaliseerde e-mailadres. - Zolang er nog geen
Users.Idbestaat, kan een uitnodiging naar een onbekend e-mailadres alleen metRelationshipInvitations.ToEmailworden vastgelegd.ToUserIdblijft dan null. - Pas nadat een account is aangemaakt en de uitnodiging nog geldig is, mag
ToUserIdworden gekoppeld en kan er eenSystemMessages-record voor de nieuwe gebruiker worden aangemaakt. - De externe identity provider maakt het account aan; de koppeling met openstaande OefenHub-uitnodigingen is OefenHub-applicatielogica.
Rolkeuze bij acceptatie van relatie-uitnodigingen
- Wanneer een relatie-uitnodiging een doelrol vereist die de gebruiker nog niet actief heeft, moet de gebruiker deze rol bewust kunnen kiezen of toevoegen binnen de acceptatieflow.
- Alleen actieve publieke rollen (
Roles.IsPublic = 1) mogen door een gebruiker zelf gekozen worden bij registratie, profieluitbreiding of acceptatie van een relatie-uitnodiging. - Niet-publieke rollen, zoals beheerrollen, mogen niet via relatie-uitnodigingen worden toegekend.
- Het toevoegen van een rol tijdens acceptatie wordt vastgelegd in
UserRolesmet auditvelden. Het ontkoppelen van een relatie trekt zo'n rol niet automatisch in.
Multi-role context
- Een gebruiker kan meerdere rollen hebben. De actieve frontendcontext bepaalt welke acties in een scherm beschikbaar zijn.
Guardianis de technische/backendwaarde voor de zichtbare rol Ouder/voogd.- Roltoekenning is niet hetzelfde als actieve schermcontext; schermen en usecases moeten server-side de juiste rolcontext controleren.
1.9 Profiel-, voorkeuren- en toegankelijkheidscontext
Deze aanvullende regels zijn afgeleid uit de generieke profielusecases voor bekijken, wijzigen, verplicht niveau, profielfoto, toegankelijkheid en voorkeuren.
1.9.1 Afbakening tussen identity provider en OefenHub-profiel
- OefenHub beheert applicatieprofielgegevens, profielafbeelding, gebruikersinstellingen, voorkeuren en actieve niveaucontext.
- Authenticatie, wachtwoorden, sessies en credential-lifecycle blijven eigendom van de externe identity provider.
- E-mailadres en wachtwoord mogen niet rechtstreeks via OefenHub-profielmutaties worden aangepast. OefenHub mag hiervoor hoogstens doorverwijzen naar de identity-providerflow.
Users.ExternalIdblijft de stabiele technische koppeling met de identity provider en is niet via de GUI wijzigbaar.- Server-side sessiecontext bepaalt altijd welk gebruikersprofiel gelezen of gewijzigd wordt; een gebruiker mag nooit via route- of formulierdata een ander profiel als eigen profiel laten verwerken.
1.9.2 Profielgegevens en naamvalidatie
- Profielwijzigingen mogen alleen de eigen OefenHub-profielvelden aanpassen.
DisplayNameblijft verplicht voor uniforme weergave in berichten, meldingen, relaties en profielweergaven.- De gereserveerde waarde
#mag niet via normale profielinvoer als tussenvoegsel worden opgeslagen, omdat deze waarde is gereserveerd voor geanonimiseerde accounts. - Een profielmutatie mag geen rollen, relaties, autorisaties, berichten, meldingen of oefenresultaten wijzigen.
1.9.3 Verplicht niveau en actieve niveaucontext
- Voor gebruikers waarvoor een actief niveau functioneel verplicht is, moet het ontbreken van
UserSettings.SelectedTeacherLevelIdals blokkade- of attenderingssituatie worden behandeld. - De gekozen waarde voor
SelectedTeacherLevelIdis een soft link en moet server-side worden gevalideerd tegen de actuele toegestane niveaucontext van de gebruiker. - Een ontbrekend verplicht niveau mag niet stilzwijgend worden vervangen door een willekeurig standaardniveau.
- Het instellen of wijzigen van de actieve niveaucontext wijzigt geen docent-leerlingrelatie en kent geen nieuwe niveau-autorisatie toe.
1.9.4 Profielafbeelding
Users.ProfileAvatarIdverwijst uitsluitend naar een actieve vooraf gedefinieerde avatar inProfileAvatars.- Vrije upload, externe avatar-URL's en gebruikersafbeeldingen buiten de vooraf goedgekeurde set zijn niet toegestaan.
- Wanneer een eerder gekozen avatar later inactief wordt, blijft historische verwijzing herleidbaar; nieuwe keuzes mogen alleen uit actieve avatars komen.
1.9.5 Toegankelijkheidsinstellingen en cookie-/browserwaarde
- Toegankelijkheidsinstellingen in
UserSettingszijn de primaire bron van waarheid zodra een gebruiker is ingelogd. - Een technische cookie of vergelijkbare browserwaarde mag toegankelijkheidskeuzes vóór login toepassen, maar vormt geen tweede bron van waarheid.
- De browserwaarde mag geen persoonsgegevens, autorisatiedata, rolcontext of profielgegevens bevatten.
- Ongeldige, ontbrekende of corrupte browserwaarden worden genegeerd en mogen geen fouttoestand veroorzaken.
- Bij login worden profielwaarden leidend; waar nodig worden relevante waarden opnieuw naar de browserwaarde gespiegeld.
- Wanneer de sitebrede toegankelijkheidsfeature uitgeschakeld is, blijven opgeslagen waarden bewaard maar worden zij functioneel genegeerd totdat de feature weer actief is.
1.9.6 Voorkeuren
- Voorkeuren wijzigen presentatiegedrag of gebruikersgedrag, maar nooit autorisaties, zichtbare gegevenssets of domeintoegang.
- Rolgebonden voorkeuren mogen alleen worden gebruikt binnen een rolcontext die de gebruiker daadwerkelijk heeft.
- Verborgen of systeemgestuurde voorkeuren, zoals
DontWarnAgainOnDunno, mogen niet als algemene vrije voorkeurensleutel via de GUI worden gemuteerd. - Voorkeuren moeten server-side worden gevalideerd op sleutel, type, toegestane waarde en eventuele rolcontext.
1.10 Accountprovisioning en account-lifecycle
1.10.1 Identity-providerafbakening
De externe identity provider blijft bronhouder voor authenticatie, registratie, wachtwoorden, wachtwoord-reset, e-mailverificatie, credential lifecycle en primaire sessie-uitgifte. OefenHub verwerkt na succesvolle authenticatie uitsluitend de applicatiecontext. OefenHub bewaart geen wachtwoorden, credentialstatus of identity-providerinterne sessiegegevens in domeintabellen.
Een succesvolle authenticatie bij de identity provider geeft pas reguliere OefenHub-toegang wanneer het interne Users-record eenduidig bestaat, actief is en naar een bruikbare applicatiecontext kan worden vertaald.
1.10.2 Users.ExternalId en provisioning
Users.ExternalId is de stabiele koppelsleutel tussen het identity-provideraccount en het interne OefenHub-account. Deze waarde wordt server-side gevuld, is niet via de GUI wijzigbaar en moet uniek zijn binnen de interne accountadministratie.
Eerste login na succesvolle identity-providerauthenticatie mag een intern Users-record aanmaken wanneer nog geen account voor dezelfde ExternalId bestaat. Deze provisioning is idempotent: opnieuw inloggen of een herhaalde callback met dezelfde ExternalId mag nooit een tweede intern account opleveren. Conflicten zoals meerdere interne records met dezelfde ExternalId blokkeren sessieopbouw en worden technisch/accountlogmatig vastgelegd.
1.10.3 UserSettings, rolcontext en frontendcontext
Bij accountprovisioning wordt een bijbehorend UserSettings-record aangemaakt of aantoonbaar geïnitialiseerd. Bij bestaande actieve accounts waarbij UserSettings ontbreekt, mag OefenHub dit veilig herstellen met toegestane defaults. Deze herstelinitialisatie mag geen rollen, relaties, autorisaties, berichten, meldingen, oefendata of niveauautorisaties wijzigen.
Na login bepaalt OefenHub server-side de beschikbare permissions en frontendcontexten op basis van Users, actieve UserRoles, actieve Roles, actieve RolePermissions, actieve Permissions, UserSettings en business rules. Clientstate, routeparameters, oude browsercontext of formulierwaarden zijn nooit bron van waarheid voor rol- of frontendcontext.
1.10.4 Gedeactiveerde, geanonimiseerde en onvolledige accounts
Users.IsActive = false blokkeert reguliere OefenHub-toegang, ook wanneer de identity provider een geldige authenticatiecontext levert. Bij succesvolle sessieverwerking voor een actief intern account wordt Users.LastSeenAtUtc bijgewerkt. Bij geweigerde login wegens gedeactiveerd of geanonimiseerd account wordt LastSeenAtUtc niet als succesvolle activiteit bijgewerkt.
Een geauthenticeerde gebruiker zonder actieve rolcontext krijgt geen reguliere leerling-, ouder/voogd-, docent- of beheerderfrontpage, maar uitsluitend de beperkte context zonder rol. Ontbrekende verplichte profiel- of niveaucontext wordt niet automatisch aangevuld; de gebruiker wordt naar de bestaande profiel- of niveauflow geleid.
1.10.5 Accountverwijdering, anonimisering en sessies
Selfservice-accountverwijdering betreft uitsluitend het eigen interne OefenHub-account. De identity provider blijft buiten deze mutatie. Functioneel leidt verwijderen direct tot anonimisering, blokkade van reguliere toegang en opruiming van afhankelijke toegang.
Bij anonimisering worden zichtbare persoonsgegevens vervangen door de vastgestelde systeemwaarden, waaronder FirstName = Anoniem, MiddleName = #, een niet-voorspelbare systeemcode in de naamweergave en een geanonimiseerd e-mailadres volgens anoniem.<code>@verwijderd.acc. De waarde # is gereserveerd voor systeemgebruik en mag niet via normale gebruikersinvoer worden opgeslagen.
Reguliere login, sessieverwerking en logout introduceren geen aparte OefenHub-domeinsessietabel. Technische sessiestate blijft onderdeel van de identity-provider- en applicatie-infrastructuur.
1.11 Beheerder- en ouder-/voogdcontext
| Onderwerp | Aanscherping |
|---|---|
| Beheerdercontext | Toegang tot beheerderusecases wordt uitsluitend server-side bepaald vanuit passende beheerpermissions en domeincontext. Clientstate, routeparameters of zichtbare menu-items mogen geen beheercontext afdwingen. |
| Combinatierollen | Wanneer een gebruiker Beheerder, Docent en/of Ouder/voogd combineert, wordt de frontpage runtime samengesteld uit permissiongedreven blokken met vaste volgorde: Beheerder, daarna Docent, daarna Ouder/voogd. De rol Leerling blijft niet combineerbaar. |
| Niet-publieke rollen | Rollen zoals Beheerder, TestDocent en de toekomstige Admin-/autorisatiebeheerrol blijven niet-publiek en mogen uitsluitend via expliciete beheer-/adminflow worden toegekend of ingetrokken. Zelfregistratie of profielwijziging mag deze rollen nooit activeren. |
| Accountstatus | Users.IsActive = false blokkeert reguliere OefenHub-toegang, maar verwijdert geen historie. Heractiveren vereist een expliciete beheerhandeling en server-side hercontrole van rollen en context. |
| Accountanonimisering | Beheerdergestuurde anonimisering beëindigt actieve toegang, rollen, relaties en afhankelijke contexten volgens domeinregels, terwijl historische audit en functionele reconstructie zonder actuele persoonsgegevens mogelijk blijven. |
| Accountgeschiedenis | Accountbeheer heeft een eigen lifecycle- en beheerlogperspectief waarin statuswijzigingen, rolmutaties, instellingencorrecties en anonimisering herleidbaar zijn. |
| Gebruikersinstellingen door beheerder | Een beheerder mag alleen expliciet ondersteunde gebruikersinstellingen corrigeren. De wijziging mag geen authenticatiegegevens, wachtwoorden, credentials of identity-providerdata aanpassen. |
| Online-status | Account-online-status is een readmodel of sessie-afgeleide waarde. Raadplegen daarvan veroorzaakt geen sessie-, rol- of profielmutatie. |
Provisioningfouten na geslaagde externe authenticatie
Wanneer de externe identity-providerlogin of -registratie slaagt, maar de interne OefenHub-provisioning of initialisatie niet volledig en consistent kan worden afgerond, mag de gebruiker geen reguliere OefenHub-sessie krijgen. De fout wordt technisch gelogd met correlatie naar de externe identiteit en eventueel het interne Users.Id wanneer dat al transactioneel of tijdelijk is ontstaan. Credentials, tokens, wachtwoorden en identity-providerinterne sessiedata worden daarbij niet opgeslagen.
Deze situatie wordt niet automatisch door de gebruiker opgelost. De gebruikersgerichte route is een generieke accountfout of contactroute, waarna beheer of technische analyse bepaalt of sprake is van een incident, dataconflict of structureel probleem.
Aanvulling - OnboardingCompletedAtUtc
identity.Users bevat een accountniveauveld OnboardingCompletedAtUtc.
| Veld | Type | Betekenis |
|---|---|---|
OnboardingCompletedAtUtc | nullable datetimeoffset | Tijdstip waarop de centrale OefenHub-onboarding voor dit account volledig is afgerond. Leeg betekent dat de onboarding-gate nog verplichte stappen moet beoordelen. |
Dit veld hoort niet in identity.UserSettings, omdat het geen gebruikersvoorkeur is maar een lifecycle-/accountstatus. De gebruiker kan deze waarde niet zelf aanpassen. Latere onboardingstappen kunnen dezelfde centrale gate uitbreiden; pas wanneer alle verplichte stappen afgerond zijn wordt dit veld gevuld.
1.12 Permissioncache en invalidatie
OefenHub gebruikt permissioncache per gebruiker om te voorkomen dat iedere autorisatiecheck een databasequery veroorzaakt.
| Onderwerp | Regel |
|---|---|
| Cachebron | Actieve UserRoles + actieve Roles + actieve RolePermissions + actieve Permissions. |
| Cachekey | Interne Users.Id plus vaste authorization-prefix. |
| Cachewaarde | Distinct set van Permissions.Code. |
| TTL | Appsettingswaarde Authorization:PermissionCacheDurationMinutes, functioneel standaard 60 minuten. |
| Cachetype | Voorlopig IMemoryCache; bij toekomstige multi-instance deployment vervangen of aanvullen met gedeelde invalidatie. |
| Login | Login probeert de cache te pre-warmen. |
| Check | Check gebruikt cache hit; bij cache miss wordt opnieuw uit de database geladen. |
| UserRole-mutatie | Leegt cache van de betrokken gebruiker. |
| RolePermission-mutatie | Leegt cache van alle gebruikers met de betrokken rol. |
| Accountstatusmutatie | Leegt cache van de betrokken gebruiker. |
| Publieke rolwijziging | Leegt cache van de betrokken gebruiker. |
Permissioncache is een performanceoptimalisatie en geen permanente autorisatiebron. De cache mag nooit in browser storage, claims of onbeveiligde clientstate worden opgeslagen.