mirror of
https://github.com/h44z/wg-portal.git
synced 2026-01-29 06:36:24 +00:00
feat: allow multiple auth sources per user (#500,#477) (#612)
* feat: allow multiple auth sources per user (#500,#477) * only override isAdmin flag if it is provided by the authentication source
This commit is contained in:
@@ -2676,6 +2676,12 @@
|
||||
"ApiTokenCreated": {
|
||||
"type": "string"
|
||||
},
|
||||
"AuthSources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"Department": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -2719,14 +2725,11 @@
|
||||
"PeerCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"PersistLocalChanges": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"Phone": {
|
||||
"type": "string"
|
||||
},
|
||||
"ProviderName": {
|
||||
"type": "string"
|
||||
},
|
||||
"Source": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -431,6 +431,10 @@ definitions:
|
||||
type: string
|
||||
ApiTokenCreated:
|
||||
type: string
|
||||
AuthSources:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
Department:
|
||||
type: string
|
||||
Disabled:
|
||||
@@ -461,12 +465,10 @@ definitions:
|
||||
type: string
|
||||
PeerCount:
|
||||
type: integer
|
||||
PersistLocalChanges:
|
||||
type: boolean
|
||||
Phone:
|
||||
type: string
|
||||
ProviderName:
|
||||
type: string
|
||||
Source:
|
||||
type: string
|
||||
type: object
|
||||
model.WebAuthnCredentialRequest:
|
||||
properties:
|
||||
|
||||
@@ -2132,6 +2132,22 @@
|
||||
"minLength": 32,
|
||||
"example": ""
|
||||
},
|
||||
"AuthSources": {
|
||||
"description": "The source of the user. This field is optional.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"db",
|
||||
"ldap",
|
||||
"oauth"
|
||||
]
|
||||
},
|
||||
"readOnly": true,
|
||||
"example": [
|
||||
"db"
|
||||
]
|
||||
},
|
||||
"Department": {
|
||||
"description": "The department of the user. This field is optional.",
|
||||
"type": "string",
|
||||
@@ -2205,22 +2221,6 @@
|
||||
"description": "The phone number of the user. This field is optional.",
|
||||
"type": "string",
|
||||
"example": "+1234546789"
|
||||
},
|
||||
"ProviderName": {
|
||||
"description": "The name of the authentication provider. This field is read-only.",
|
||||
"type": "string",
|
||||
"readOnly": true,
|
||||
"example": ""
|
||||
},
|
||||
"Source": {
|
||||
"description": "The source of the user. This field is optional.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"db",
|
||||
"ldap",
|
||||
"oauth"
|
||||
],
|
||||
"example": "db"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -490,6 +490,18 @@ definitions:
|
||||
maxLength: 64
|
||||
minLength: 32
|
||||
type: string
|
||||
AuthSources:
|
||||
description: The source of the user. This field is optional.
|
||||
example:
|
||||
- db
|
||||
items:
|
||||
enum:
|
||||
- db
|
||||
- ldap
|
||||
- oauth
|
||||
type: string
|
||||
readOnly: true
|
||||
type: array
|
||||
Department:
|
||||
description: The department of the user. This field is optional.
|
||||
example: Software Development
|
||||
@@ -552,19 +564,6 @@ definitions:
|
||||
description: The phone number of the user. This field is optional.
|
||||
example: "+1234546789"
|
||||
type: string
|
||||
ProviderName:
|
||||
description: The name of the authentication provider. This field is read-only.
|
||||
example: ""
|
||||
readOnly: true
|
||||
type: string
|
||||
Source:
|
||||
description: The source of the user. This field is optional.
|
||||
enum:
|
||||
- db
|
||||
- ldap
|
||||
- oauth
|
||||
example: db
|
||||
type: string
|
||||
required:
|
||||
- Identifier
|
||||
type: object
|
||||
|
||||
@@ -3,6 +3,7 @@ package backend
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
@@ -95,8 +96,10 @@ func (u UserService) ChangePassword(
|
||||
}
|
||||
|
||||
// ensure that the user uses the database backend; otherwise we can't change the password
|
||||
if user.Source != domain.UserSourceDatabase {
|
||||
return nil, fmt.Errorf("user source %s does not support password changes", user.Source)
|
||||
if !slices.ContainsFunc(user.Authentications, func(authentication domain.UserAuthentication) bool {
|
||||
return authentication.Source == domain.UserSourceDatabase
|
||||
}) {
|
||||
return nil, fmt.Errorf("user has no linked authentication source that does support password changes")
|
||||
}
|
||||
|
||||
// validate old password
|
||||
|
||||
@@ -3,15 +3,15 @@ package model
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Identifier string `json:"Identifier"`
|
||||
Email string `json:"Email"`
|
||||
Source string `json:"Source"`
|
||||
ProviderName string `json:"ProviderName"`
|
||||
IsAdmin bool `json:"IsAdmin"`
|
||||
Identifier string `json:"Identifier"`
|
||||
Email string `json:"Email"`
|
||||
AuthSources []string `json:"AuthSources"`
|
||||
IsAdmin bool `json:"IsAdmin"`
|
||||
|
||||
Firstname string `json:"Firstname"`
|
||||
Lastname string `json:"Lastname"`
|
||||
@@ -29,6 +29,8 @@ type User struct {
|
||||
ApiTokenCreated *time.Time `json:"ApiTokenCreated,omitempty"`
|
||||
ApiEnabled bool `json:"ApiEnabled"`
|
||||
|
||||
PersistLocalChanges bool `json:"PersistLocalChanges"`
|
||||
|
||||
// Calculated
|
||||
|
||||
PeerCount int `json:"PeerCount"`
|
||||
@@ -36,24 +38,26 @@ type User struct {
|
||||
|
||||
func NewUser(src *domain.User, exposeCreds bool) *User {
|
||||
u := &User{
|
||||
Identifier: string(src.Identifier),
|
||||
Email: src.Email,
|
||||
Source: string(src.Source),
|
||||
ProviderName: src.ProviderName,
|
||||
IsAdmin: src.IsAdmin,
|
||||
Firstname: src.Firstname,
|
||||
Lastname: src.Lastname,
|
||||
Phone: src.Phone,
|
||||
Department: src.Department,
|
||||
Notes: src.Notes,
|
||||
Password: "", // never fill password
|
||||
Disabled: src.IsDisabled(),
|
||||
DisabledReason: src.DisabledReason,
|
||||
Locked: src.IsLocked(),
|
||||
LockedReason: src.LockedReason,
|
||||
ApiToken: "", // by default, do not expose API token
|
||||
ApiTokenCreated: src.ApiTokenCreated,
|
||||
ApiEnabled: src.IsApiEnabled(),
|
||||
Identifier: string(src.Identifier),
|
||||
Email: src.Email,
|
||||
AuthSources: internal.Map(src.Authentications, func(authentication domain.UserAuthentication) string {
|
||||
return string(authentication.Source)
|
||||
}),
|
||||
IsAdmin: src.IsAdmin,
|
||||
Firstname: src.Firstname,
|
||||
Lastname: src.Lastname,
|
||||
Phone: src.Phone,
|
||||
Department: src.Department,
|
||||
Notes: src.Notes,
|
||||
Password: "", // never fill password
|
||||
Disabled: src.IsDisabled(),
|
||||
DisabledReason: src.DisabledReason,
|
||||
Locked: src.IsLocked(),
|
||||
LockedReason: src.LockedReason,
|
||||
ApiToken: "", // by default, do not expose API token
|
||||
ApiTokenCreated: src.ApiTokenCreated,
|
||||
ApiEnabled: src.IsApiEnabled(),
|
||||
PersistLocalChanges: src.PersistLocalChanges,
|
||||
|
||||
PeerCount: src.LinkedPeerCount,
|
||||
}
|
||||
@@ -77,22 +81,21 @@ func NewUsers(src []domain.User) []User {
|
||||
func NewDomainUser(src *User) *domain.User {
|
||||
now := time.Now()
|
||||
res := &domain.User{
|
||||
Identifier: domain.UserIdentifier(src.Identifier),
|
||||
Email: src.Email,
|
||||
Source: domain.UserSource(src.Source),
|
||||
ProviderName: src.ProviderName,
|
||||
IsAdmin: src.IsAdmin,
|
||||
Firstname: src.Firstname,
|
||||
Lastname: src.Lastname,
|
||||
Phone: src.Phone,
|
||||
Department: src.Department,
|
||||
Notes: src.Notes,
|
||||
Password: domain.PrivateString(src.Password),
|
||||
Disabled: nil, // set below
|
||||
DisabledReason: src.DisabledReason,
|
||||
Locked: nil, // set below
|
||||
LockedReason: src.LockedReason,
|
||||
LinkedPeerCount: src.PeerCount,
|
||||
Identifier: domain.UserIdentifier(src.Identifier),
|
||||
Email: src.Email,
|
||||
IsAdmin: src.IsAdmin,
|
||||
Firstname: src.Firstname,
|
||||
Lastname: src.Lastname,
|
||||
Phone: src.Phone,
|
||||
Department: src.Department,
|
||||
Notes: src.Notes,
|
||||
Password: domain.PrivateString(src.Password),
|
||||
Disabled: nil, // set below
|
||||
DisabledReason: src.DisabledReason,
|
||||
Locked: nil, // set below
|
||||
LockedReason: src.LockedReason,
|
||||
LinkedPeerCount: src.PeerCount,
|
||||
PersistLocalChanges: src.PersistLocalChanges,
|
||||
}
|
||||
|
||||
if src.Disabled {
|
||||
|
||||
@@ -3,6 +3,7 @@ package models
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
@@ -13,9 +14,7 @@ type User struct {
|
||||
// The email address of the user. This field is optional.
|
||||
Email string `json:"Email" binding:"omitempty,email" example:"test@test.com"`
|
||||
// The source of the user. This field is optional.
|
||||
Source string `json:"Source" binding:"oneof=db ldap oauth" example:"db"`
|
||||
// The name of the authentication provider. This field is read-only.
|
||||
ProviderName string `json:"ProviderName,omitempty" readonly:"true" example:""`
|
||||
AuthSources []string `json:"AuthSources" readonly:"true" binding:"oneof=db ldap oauth" example:"db"`
|
||||
// If this field is set, the user is an admin.
|
||||
IsAdmin bool `json:"IsAdmin" example:"false"`
|
||||
|
||||
@@ -52,10 +51,11 @@ type User struct {
|
||||
|
||||
func NewUser(src *domain.User, exposeCredentials bool) *User {
|
||||
u := &User{
|
||||
Identifier: string(src.Identifier),
|
||||
Email: src.Email,
|
||||
Source: string(src.Source),
|
||||
ProviderName: src.ProviderName,
|
||||
Identifier: string(src.Identifier),
|
||||
Email: src.Email,
|
||||
AuthSources: internal.Map(src.Authentications, func(authentication domain.UserAuthentication) string {
|
||||
return string(authentication.Source)
|
||||
}),
|
||||
IsAdmin: src.IsAdmin,
|
||||
Firstname: src.Firstname,
|
||||
Lastname: src.Lastname,
|
||||
@@ -93,8 +93,6 @@ func NewDomainUser(src *User) *domain.User {
|
||||
res := &domain.User{
|
||||
Identifier: domain.UserIdentifier(src.Identifier),
|
||||
Email: src.Email,
|
||||
Source: domain.UserSource(src.Source),
|
||||
ProviderName: src.ProviderName,
|
||||
IsAdmin: src.IsAdmin,
|
||||
Firstname: src.Firstname,
|
||||
Lastname: src.Lastname,
|
||||
|
||||
Reference in New Issue
Block a user