feat: add LDAP support and guest user functionality

- Introduced BaseDN configuration for LDAP in the config.
- Enhanced User model to include IsLDAP field.
- Updated database queries to handle LDAP users.
- Implemented GraphQL queries for LDAP browsing and user authentication type.
- Added ACL management for users and groups, including guest user permissions.
- Updated frontend to support LDAP login and guest user login.
- Improved error handling and user feedback in login and ACL management.
This commit is contained in:
2026-04-15 00:42:40 +02:00
parent 61f33d350a
commit d571f156a6
9 changed files with 710 additions and 93 deletions

View File

@@ -139,18 +139,56 @@ Backenden använder **inte** gqlgen-genererad kod i produktion. `server.go` impl
### Autentisering och sessioner
```
Login-flöde:
1. Klient: SHA-256(username:password) → skickas som "password"
2. Server: bcrypt.CompareHashAndPassword(stored_hash, received_digest)
3. Vid match: slumpmässig hex-token genereras och sparas i minnet
4. Token returneras till klient, lagras i localStorage
5. Alla efterföljande requests: Authorization: Bearer <token>
Login-flöde (lokalt konto):
1. Frontend: kontrollerar authType (query userAuthType) → "local"
2. Frontend: SHA-256(username:password) → skickas som "password"
3. Backend: bcrypt.CompareHashAndPassword(stored_hash, received_digest)
4. Vid match: slumpmässig hex-token genereras och sparas i minnet
5. Token returneras till klient, lagras i localStorage
Login-flöde (LDAP-konto):
1. Frontend: kontrollerar authType → "ldap"
2. Frontend: skickar klartextlösenord i fältet "ldapPassword" (EJ hashat)
3. Backend: hämtar användarens DN via LDAP-sökning (baseDN + filter)
4. Backend: binder till LDAP med DN + klartextlösenord (ldapBind)
5. Vid lyckad bind: session skapas med roll "user"
Login-flöde (gäst):
1. Frontend: klickar "Continue as Guest" → skickar username="guest", password=""
2. Backend: identifierar gäst, skapar session utan lösenordskontroll, roll "guest"
3. Gäst ser bara dokument/mappar som admin explicit gett tillåtelse via ACL
Session-storage: in-memory map[token]*Session (försvinner vid omstart)
Session-livstid: 7 dagar (konfigurerat i auth.go)
Session-livstid: 24 timmar (konfigurerat i auth.go)
```
**Viktigt:** Sessions lagras enbart i minnet. Vid server-omstart måste alla klienter logga in igen.
**Viktigt:** Sessions lagras enbart i minnet. Vid server-omstart måste alla klienter logga in igen. LDAP-konfigurationsändringar (via admin-panelen) nollställer **inte** sessioner — befintliga sessioner är fortfarande giltiga.
### Gästanvändare
En inbyggd `guest`-användare skapas automatiskt vid installation och vid serverstart (migrations-safe). Gästen:
- Kräver inget lösenord
- Har roll `"guest"` (skild från `"user"` och `"admin"`)
- Kan som standard inte se något
- Admin ger tillgång per dokument eller mapp via ACL-panelen
### Åtkomstkontroll (ACL)
ACL-poster lagras i SQLite-tabellen `acl`. Varje post kopplar en sökväg (slug eller mappnamn) till ett subjekt (användare eller grupp) och sju boolean-flaggor:
| Flagga | Innebär |
|------------|----------------------------------------|
| canSearch | Syns i sökning/listning |
| canView | Syns i mapplistning |
| canRead | Kan öppna och läsa innehållet |
| canEdit | Kan redigera och spara |
| canCreate | Kan skapa nya dokument i mappen |
| canDelete | Kan radera |
| canMove | Kan flytta/byta namn |
Sökvägar är hierarkiska: om admin ger gästen tillgång till mappen `docs` räcker det för alla dokument under `docs/`.
Enforcement sker i `handleDocuments` och `handleFolders` — gästanvändare filtreras mot ACL. Admin-användare kringgår alltid ACL-kontroller.
### Setup-detektion — `needsSetup`
@@ -179,7 +217,11 @@ resolveDBPath(configPath):
- `db_path` — sökväg till SQLite-fil
- `jwt_secret` — används för session-token generation (ej JWT i klassisk mening)
- `listen_addr` — TCP-adress att lyssna på, t.ex. `:4000`
- `ldap` — valfri LDAP-konfiguration
- `ldap` — valfri LDAP-konfiguration:
- `url` — LDAP-server URL, t.ex. `ldap://openldap:389`
- `base_dn` — rot-DN att söka ifrån, t.ex. `dc=example,dc=com`
- `admin_user` — tjänstekonto DN för uppslag (valfritt)
- `admin_pass` — tjänstekontoets lösenord (valfritt)
Om filen saknas returnerar `Load()` `ErrRequireSetup` — backenden startar i setup-läge.
@@ -238,28 +280,37 @@ Ordningen är viktig: om DB-skapandet eller user-insert misslyckas sparas **inge
### 4.3 Inloggningsflöde
```
Användaren fyller i användarnamn + lösenord
Användaren fyller i användarnamn + lösenord (eller klickar "Continue as Guest")
app.login(username, password)
├─ SHA-256(lowercase(username):password) → hashed
├─ query userAuthType(username) → "local" | "ldap" | "guest"
POST /graphql mutation Login($u, $p: hashed)
authType == "local":
│ SHA-256(lowercase(username):password) → hashed
│ → POST /graphql mutation Login(u, p: hashed)
├─ authType == "ldap":
│ → POST /graphql mutation Login(u, p: "", ldapPassword: plaintext)
└─ authType == "guest":
→ POST /graphql mutation Login(u: "guest", p: "")
handleLogin (server.go)
├─ db.GetUser(username) → lokal SQLite
│ ├─ hittad: bcrypt.Compare(stored, hashed) → OK/fel
│ └─ ej hittad + LDAP konfigurerat → ldapBind test
├─ "guest": returnerar token utan lösenordskontroll
├─ Skapar slumpmässig session-token
└─ Returnerar token
├─ "ldap" (is_ldap=1 i DB): ldapBind(username, ldapPassword)
│ → OK: session med roll "user"
Klient sparar token i localStorage
AppLayout renderas
└─ "local" (is_ldap=0): bcrypt.Compare(stored, hashed)
→ OK: session med roll baserad på DB-värde
Gäst-session:
- Roll "guest" — se ACL-enforcement i handleDocuments / handleFolders
- Kan bara se dokument/mappar som admin explicit beviljat via ACL
```
### 4.4 Spara dokument
@@ -366,30 +417,37 @@ Archivum/
**GraphQL-schema (urval):**
```graphql
type Query {
systemStatus: SystemStatus! # OK | REQUIRE_SETUP
serverDirectories(path: String): [ServerDirectory!]! # Listar mappar (inkl. isGitRepo)
systemStatus: SystemStatus!
userAuthType(username: String!): String! # "local" | "ldap" | "guest"
documents(prefix: String): [DocumentMeta!]!
document(slug: String!): Document
folders: [String!]! # Listar alla mappvägar i repositoriet
folders: [String!]!
history(slug: String!): [CommitEntry!]!
diff(slug: String!, fromHash: String!, toHash: String!): String!
}
type ServerDirectory {
name: String!
path: String!
isGitRepo: Boolean!
hasDocuments: Boolean!
acl(path: String!): [ACLEntry!]!
aclSubjects: ACLSubjects! # Användare + grupper för ACL-picker
ldapBrowse(url: String, baseDN: String, adminUser: String, adminPass: String): LDAPTree!
}
type Mutation {
setup(input: SetupInput!): Boolean!
login(username: String!, password: String!): String! # returnerar session-token
login(username: String!, password: String!, ldapPassword: String): String!
logout: Boolean!
setup(input: SetupInput!): Boolean!
saveDocument(input: SaveDocumentInput!): Document!
deleteDocument(slug: String!): Boolean!
createFolder(path: String!): Boolean! # Skapar en ny mapp (katalog)
moveDocument(oldSlug: String!, newSlug: String!): Boolean! # Flyttar ett dokument till en annan plats
updateLdapConfig(input: LDAPInput): Boolean!
importLdapSubject(type: String!, name: String!): Boolean!
setAcl(input: ACLInput!): Boolean!
removeAcl(id: Int!): Boolean!
createFolder(path: String!): Boolean!
moveDocument(oldSlug: String!, newSlug: String!): Boolean!
}
input LDAPInput {
url: String!
baseDN: String
adminUser: String!
adminPass: String # Utelämnas → befintligt lösenord behålls
}
```