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:
126
ARCHITECTURE.md
126
ARCHITECTURE.md
@@ -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
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user