mirror of
https://github.com/h44z/wg-portal
synced 2025-02-26 05:49:14 +00:00
fix plain oauth login (#317)
This commit is contained in:
parent
378252ba2f
commit
62dbdfe0f9
@ -94,7 +94,6 @@ The following configuration options are available:
|
|||||||
| auth_type | mail | plain | SMTP authentication type, allowed values: plain, login, crammd5. |
|
| auth_type | mail | plain | SMTP authentication type, allowed values: plain, login, crammd5. |
|
||||||
| from | mail | Wireguard Portal <noreply@wireguard.local> | The address that is used to send mails. |
|
| from | mail | Wireguard Portal <noreply@wireguard.local> | The address that is used to send mails. |
|
||||||
| link_only | mail | false | Only send links to WireGuard Portal instead of the full configuration. |
|
| link_only | mail | false | Only send links to WireGuard Portal instead of the full configuration. |
|
||||||
| callback_url_prefix | auth | /api/v0 | OAuth callback URL prefix. The full callback URL will look like: https://wg.portal.local/callback_url_prefix/provider_name/callback |
|
|
||||||
| oidc | auth | Empty Array - no providers configured | A list of OpenID Connect providers. See auth/oidc properties to setup a new provider. |
|
| oidc | auth | Empty Array - no providers configured | A list of OpenID Connect providers. See auth/oidc properties to setup a new provider. |
|
||||||
| oauth | auth | Empty Array - no providers configured | A list of plain OAuth providers. See auth/oauth properties to setup a new provider. |
|
| oauth | auth | Empty Array - no providers configured | A list of plain OAuth providers. See auth/oauth properties to setup a new provider. |
|
||||||
| ldap | auth | Empty Array - no providers configured | A list of LDAP providers. See auth/ldap properties to setup a new provider. |
|
| ldap | auth | Empty Array - no providers configured | A list of LDAP providers. See auth/ldap properties to setup a new provider. |
|
||||||
@ -108,12 +107,10 @@ The following configuration options are available:
|
|||||||
| registration_enabled | auth/oidc | | If registration is enabled, new user accounts will created in WireGuard Portal. |
|
| registration_enabled | auth/oidc | | If registration is enabled, new user accounts will created in WireGuard Portal. |
|
||||||
| provider_name | auth/oauth | | A unique provider name. This name must be unique throughout all authentication providers (even other types). |
|
| provider_name | auth/oauth | | A unique provider name. This name must be unique throughout all authentication providers (even other types). |
|
||||||
| display_name | auth/oauth | | The display name is shown at the login page (the login button). |
|
| display_name | auth/oauth | | The display name is shown at the login page (the login button). |
|
||||||
| base_url | auth/oauth | | The base_url is the URL identifier for the service. For example: "https://accounts.google.com". |
|
|
||||||
| client_id | auth/oauth | | The OAuth client id. |
|
| client_id | auth/oauth | | The OAuth client id. |
|
||||||
| client_secret | auth/oauth | | The OAuth client secret. |
|
| client_secret | auth/oauth | | The OAuth client secret. |
|
||||||
| auth_url | auth/oauth | | The URL for the authentication endpoint. |
|
| auth_url | auth/oauth | | The URL for the authentication endpoint. |
|
||||||
| token_url | auth/oauth | | The URL for the token endpoint. |
|
| token_url | auth/oauth | | The URL for the token endpoint. |
|
||||||
| redirect_url | auth/oauth | | The redirect URL. |
|
|
||||||
| user_info_url | auth/oauth | | The URL for the user information endpoint. |
|
| user_info_url | auth/oauth | | The URL for the user information endpoint. |
|
||||||
| scopes | auth/oauth | | OAuth scopes. |
|
| scopes | auth/oauth | | OAuth scopes. |
|
||||||
| field_map | auth/oauth | | Mapping of user fields. Internal fields: user_identifier, email, firstname, lastname, phone, department and is_admin. |
|
| field_map | auth/oauth | | Mapping of user fields. Internal fields: user_identifier, email, firstname, lastname, phone, department and is_admin. |
|
||||||
|
@ -72,7 +72,7 @@ func main() {
|
|||||||
userManager, err := users.NewUserManager(cfg, eventBus, database, database)
|
userManager, err := users.NewUserManager(cfg, eventBus, database, database)
|
||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
|
|
||||||
authenticator, err := auth.NewAuthenticator(&cfg.Auth, eventBus, userManager)
|
authenticator, err := auth.NewAuthenticator(&cfg.Auth, cfg.Web.ExternalUrl, eventBus, userManager)
|
||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
|
|
||||||
wireGuardManager, err := wireguard.NewWireGuardManager(cfg, eventBus, wireGuard, wgQuick, database)
|
wireGuardManager, err := wireguard.NewWireGuardManager(cfg, eventBus, wireGuard, wgQuick, database)
|
||||||
|
@ -12,7 +12,6 @@ web:
|
|||||||
request_logging: true
|
request_logging: true
|
||||||
|
|
||||||
auth:
|
auth:
|
||||||
callback_url_prefix: http://localhost:8888/api/v0
|
|
||||||
ldap:
|
ldap:
|
||||||
- id: ldap1
|
- id: ldap1
|
||||||
provider_name: company ldap
|
provider_name: company ldap
|
||||||
@ -46,4 +45,23 @@ auth:
|
|||||||
extra_scopes:
|
extra_scopes:
|
||||||
- https://www.googleapis.com/auth/userinfo.email
|
- https://www.googleapis.com/auth/userinfo.email
|
||||||
- https://www.googleapis.com/auth/userinfo.profile
|
- https://www.googleapis.com/auth/userinfo.profile
|
||||||
|
registration_enabled: true
|
||||||
|
oauth:
|
||||||
|
- id: google_plain_oauth
|
||||||
|
provider_name: google3
|
||||||
|
display_name: Login with</br>Google3
|
||||||
|
client_id: another-client-id-1234.apps.googleusercontent.com
|
||||||
|
client_secret: A_CLIENT_SECRET
|
||||||
|
auth_url: https://accounts.google.com/o/oauth2/v2/auth
|
||||||
|
token_url: https://oauth2.googleapis.com/token
|
||||||
|
user_info_url: https://openidconnect.googleapis.com/v1/userinfo
|
||||||
|
scopes:
|
||||||
|
- openid
|
||||||
|
- email
|
||||||
|
- profile
|
||||||
|
field_map:
|
||||||
|
email: email
|
||||||
|
firstname: name
|
||||||
|
user_identifier: sub
|
||||||
|
is_admin: roles
|
||||||
registration_enabled: true
|
registration_enabled: true
|
@ -6,8 +6,6 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/h44z/wg-portal/internal/app"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
@ -15,10 +13,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
evbus "github.com/vardius/message-bus"
|
"github.com/h44z/wg-portal/internal/app"
|
||||||
|
|
||||||
"github.com/h44z/wg-portal/internal/config"
|
"github.com/h44z/wg-portal/internal/config"
|
||||||
"github.com/h44z/wg-portal/internal/domain"
|
"github.com/h44z/wg-portal/internal/domain"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
evbus "github.com/vardius/message-bus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserManager interface {
|
type UserManager interface {
|
||||||
@ -33,14 +32,21 @@ type Authenticator struct {
|
|||||||
oauthAuthenticators map[string]domain.OauthAuthenticator
|
oauthAuthenticators map[string]domain.OauthAuthenticator
|
||||||
ldapAuthenticators map[string]domain.LdapAuthenticator
|
ldapAuthenticators map[string]domain.LdapAuthenticator
|
||||||
|
|
||||||
|
// URL prefix for the callback endpoints, this is a combination of the external URL and the API prefix
|
||||||
|
callbackUrlPrefix string
|
||||||
|
|
||||||
users UserManager
|
users UserManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthenticator(cfg *config.Auth, bus evbus.MessageBus, users UserManager) (*Authenticator, error) {
|
func NewAuthenticator(cfg *config.Auth, extUrl string, bus evbus.MessageBus, users UserManager) (
|
||||||
|
*Authenticator,
|
||||||
|
error,
|
||||||
|
) {
|
||||||
a := &Authenticator{
|
a := &Authenticator{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
bus: bus,
|
bus: bus,
|
||||||
users: users,
|
users: users,
|
||||||
|
callbackUrlPrefix: fmt.Sprintf("%s/api/v0", extUrl),
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
@ -55,7 +61,7 @@ func NewAuthenticator(cfg *config.Auth, bus evbus.MessageBus, users UserManager)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Authenticator) setupExternalAuthProviders(ctx context.Context) error {
|
func (a *Authenticator) setupExternalAuthProviders(ctx context.Context) error {
|
||||||
extUrl, err := url.Parse(a.cfg.CallbackUrlPrefix)
|
extUrl, err := url.Parse(a.callbackUrlPrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse external url: %w", err)
|
return fmt.Errorf("failed to parse external url: %w", err)
|
||||||
}
|
}
|
||||||
@ -141,8 +147,8 @@ func (a *Authenticator) GetExternalLoginProviders(_ context.Context) []domain.Lo
|
|||||||
authProviders = append(authProviders, domain.LoginProviderInfo{
|
authProviders = append(authProviders, domain.LoginProviderInfo{
|
||||||
Identifier: providerId,
|
Identifier: providerId,
|
||||||
Name: providerName,
|
Name: providerName,
|
||||||
ProviderUrl: fmt.Sprintf("%s/%s/init", a.cfg.CallbackUrlPrefix, providerId),
|
ProviderUrl: fmt.Sprintf("/auth/login/%s/init", providerId),
|
||||||
CallbackUrl: fmt.Sprintf("%s/%s/callback", a.cfg.CallbackUrlPrefix, providerId),
|
CallbackUrl: fmt.Sprintf("/auth/login/%s/callback", providerId),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,8 +193,13 @@ func (a *Authenticator) PlainLogin(ctx context.Context, username, password strin
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Authenticator) passwordAuthentication(ctx context.Context, identifier domain.UserIdentifier, password string) (*domain.User, error) {
|
func (a *Authenticator) passwordAuthentication(
|
||||||
ctx = domain.SetUserInfo(ctx, domain.SystemAdminContextUserInfo()) // switch to admin user context to check if user exists
|
ctx context.Context,
|
||||||
|
identifier domain.UserIdentifier,
|
||||||
|
password string,
|
||||||
|
) (*domain.User, error) {
|
||||||
|
ctx = domain.SetUserInfo(ctx,
|
||||||
|
domain.SystemAdminContextUserInfo()) // switch to admin user context to check if user exists
|
||||||
|
|
||||||
var ldapUserInfo *domain.AuthenticatorUserInfo
|
var ldapUserInfo *domain.AuthenticatorUserInfo
|
||||||
var ldapProvider domain.LdapAuthenticator
|
var ldapProvider domain.LdapAuthenticator
|
||||||
@ -248,7 +259,8 @@ func (a *Authenticator) passwordAuthentication(ctx context.Context, identifier d
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !userInDatabase {
|
if !userInDatabase {
|
||||||
user, err := a.processUserInfo(ctx, ldapUserInfo, domain.UserSourceLdap, ldapProvider.GetName(), ldapProvider.RegistrationEnabled())
|
user, err := a.processUserInfo(ctx, ldapUserInfo, domain.UserSourceLdap, ldapProvider.GetName(),
|
||||||
|
ldapProvider.RegistrationEnabled())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to process user information: %w", err)
|
return nil, fmt.Errorf("unable to process user information: %w", err)
|
||||||
}
|
}
|
||||||
@ -262,7 +274,10 @@ func (a *Authenticator) passwordAuthentication(ctx context.Context, identifier d
|
|||||||
|
|
||||||
// region oauth authentication
|
// region oauth authentication
|
||||||
|
|
||||||
func (a *Authenticator) OauthLoginStep1(_ context.Context, providerId string) (authCodeUrl, state, nonce string, err error) {
|
func (a *Authenticator) OauthLoginStep1(_ context.Context, providerId string) (
|
||||||
|
authCodeUrl, state, nonce string,
|
||||||
|
err error,
|
||||||
|
) {
|
||||||
oauthProvider, ok := a.oauthAuthenticators[providerId]
|
oauthProvider, ok := a.oauthAuthenticators[providerId]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", "", "", fmt.Errorf("missing oauth provider %s", providerId)
|
return "", "", "", fmt.Errorf("missing oauth provider %s", providerId)
|
||||||
@ -318,8 +333,10 @@ func (a *Authenticator) OauthLoginStep2(ctx context.Context, providerId, nonce,
|
|||||||
return nil, fmt.Errorf("unable to parse user information: %w", err)
|
return nil, fmt.Errorf("unable to parse user information: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = domain.SetUserInfo(ctx, domain.SystemAdminContextUserInfo()) // switch to admin user context to check if user exists
|
ctx = domain.SetUserInfo(ctx,
|
||||||
user, err := a.processUserInfo(ctx, userInfo, domain.UserSourceOauth, oauthProvider.GetName(), oauthProvider.RegistrationEnabled())
|
domain.SystemAdminContextUserInfo()) // switch to admin user context to check if user exists
|
||||||
|
user, err := a.processUserInfo(ctx, userInfo, domain.UserSourceOauth, oauthProvider.GetName(),
|
||||||
|
oauthProvider.RegistrationEnabled())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to process user information: %w", err)
|
return nil, fmt.Errorf("unable to process user information: %w", err)
|
||||||
}
|
}
|
||||||
@ -333,7 +350,13 @@ func (a *Authenticator) OauthLoginStep2(ctx context.Context, providerId, nonce,
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Authenticator) processUserInfo(ctx context.Context, userInfo *domain.AuthenticatorUserInfo, source domain.UserSource, provider string, withReg bool) (*domain.User, error) {
|
func (a *Authenticator) processUserInfo(
|
||||||
|
ctx context.Context,
|
||||||
|
userInfo *domain.AuthenticatorUserInfo,
|
||||||
|
source domain.UserSource,
|
||||||
|
provider string,
|
||||||
|
withReg bool,
|
||||||
|
) (*domain.User, error) {
|
||||||
// Search user in backend
|
// Search user in backend
|
||||||
user, err := a.users.GetUser(ctx, userInfo.Identifier)
|
user, err := a.users.GetUser(ctx, userInfo.Identifier)
|
||||||
switch {
|
switch {
|
||||||
@ -349,7 +372,12 @@ func (a *Authenticator) processUserInfo(ctx context.Context, userInfo *domain.Au
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Authenticator) registerNewUser(ctx context.Context, userInfo *domain.AuthenticatorUserInfo, source domain.UserSource, provider string) (*domain.User, error) {
|
func (a *Authenticator) registerNewUser(
|
||||||
|
ctx context.Context,
|
||||||
|
userInfo *domain.AuthenticatorUserInfo,
|
||||||
|
source domain.UserSource,
|
||||||
|
provider string,
|
||||||
|
) (*domain.User, error) {
|
||||||
// convert user info to domain.User
|
// convert user info to domain.User
|
||||||
user := &domain.User{
|
user := &domain.User{
|
||||||
Identifier: userInfo.Identifier,
|
Identifier: userInfo.Identifier,
|
||||||
|
@ -7,10 +7,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
OpenIDConnect []OpenIDConnectProvider `yaml:"oidc"`
|
OpenIDConnect []OpenIDConnectProvider `yaml:"oidc"`
|
||||||
OAuth []OAuthProvider `yaml:"oauth"`
|
OAuth []OAuthProvider `yaml:"oauth"`
|
||||||
Ldap []LdapProvider `yaml:"ldap"`
|
Ldap []LdapProvider `yaml:"ldap"`
|
||||||
CallbackUrlPrefix string `yaml:"callback_url_prefix"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseFields struct {
|
type BaseFields struct {
|
||||||
@ -24,7 +23,7 @@ type BaseFields struct {
|
|||||||
|
|
||||||
type OauthFields struct {
|
type OauthFields struct {
|
||||||
BaseFields `yaml:",inline"`
|
BaseFields `yaml:",inline"`
|
||||||
IsAdmin string `yaml:"is_admin"`
|
IsAdmin string `yaml:"is_admin"` // If the value is "true", the user is an admin.
|
||||||
}
|
}
|
||||||
|
|
||||||
type LdapFields struct {
|
type LdapFields struct {
|
||||||
@ -93,8 +92,6 @@ type OAuthProvider struct {
|
|||||||
// DisplayName is shown to the user on the login page. If it is empty, ProviderName will be displayed.
|
// DisplayName is shown to the user on the login page. If it is empty, ProviderName will be displayed.
|
||||||
DisplayName string `yaml:"display_name"`
|
DisplayName string `yaml:"display_name"`
|
||||||
|
|
||||||
BaseUrl string `yaml:"base_url"`
|
|
||||||
|
|
||||||
// ClientID is the application's ID.
|
// ClientID is the application's ID.
|
||||||
ClientID string `yaml:"client_id"`
|
ClientID string `yaml:"client_id"`
|
||||||
|
|
||||||
@ -105,10 +102,6 @@ type OAuthProvider struct {
|
|||||||
TokenURL string `yaml:"token_url"`
|
TokenURL string `yaml:"token_url"`
|
||||||
UserInfoURL string `yaml:"user_info_url"`
|
UserInfoURL string `yaml:"user_info_url"`
|
||||||
|
|
||||||
// RedirectURL is the URL to redirect users going through
|
|
||||||
// the OAuth flow, after the resource owner's URLs.
|
|
||||||
RedirectURL string `yaml:"redirect_url"`
|
|
||||||
|
|
||||||
// Scope specifies optional requested permissions.
|
// Scope specifies optional requested permissions.
|
||||||
Scopes []string `yaml:"scopes"`
|
Scopes []string `yaml:"scopes"`
|
||||||
|
|
||||||
|
@ -104,8 +104,6 @@ func defaultConfig() *Config {
|
|||||||
SiteCompanyName: "WireGuard Portal",
|
SiteCompanyName: "WireGuard Portal",
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.Auth.CallbackUrlPrefix = "/api/v0"
|
|
||||||
|
|
||||||
cfg.Advanced.StartListenPort = 51820
|
cfg.Advanced.StartListenPort = 51820
|
||||||
cfg.Advanced.StartCidrV4 = "10.11.12.0/24"
|
cfg.Advanced.StartCidrV4 = "10.11.12.0/24"
|
||||||
cfg.Advanced.StartCidrV6 = "fdfd:d3ad:c0de:1234::0/64"
|
cfg.Advanced.StartCidrV6 = "fdfd:d3ad:c0de:1234::0/64"
|
||||||
|
Loading…
Reference in New Issue
Block a user