Compare commits

...

13 Commits

Author SHA1 Message Date
Christoph Haas
08c8f8eac0
backport username display bugfix (#456) 2025-06-12 19:11:25 +02:00
Christoph Haas
d864e24145
improve logging of OAuth login issues, decrease auth-code exchange timeout (#451)
(cherry picked from commit e3b65ca3370cdad96c414047a0a297fe41fe0119)
2025-06-12 19:07:46 +02:00
Christoph Haas
5b56e58fe9
fix self-provisioned peer-generation (#452)
(cherry picked from commit 61d8aa658996cf563a1a5c2db1d99bcfc7de7162)
2025-06-09 17:41:29 +02:00
Christoph Haas
930ef7b573
Merge branch 'master' into stable 2025-05-16 09:58:14 +02:00
Christoph Haas
18296673d7
Merge branch 'master' into stable 2025-05-13 20:25:27 +02:00
Christoph Haas
4ccc59c109 Merge branch 'master' into stable
# Conflicts:
#	.github/workflows/docker-publish.yml
#	README.md
#	assets/tpl/admin_index.html
#	assets/tpl/user_index.html
#	cmd/wg-portal/main.go
#	docker-compose.yml
#	go.mod
#	go.sum
#	internal/common/util.go
#	internal/server/docs/docs.go
#	internal/server/handlers_common.go
#	internal/server/server.go
#	internal/wireguard/peermanager.go
2025-05-04 20:38:55 +02:00
onyx-flame
e6b01a9903
Feature (v1): add latest handshake data to API response (#203)
* feature: updated handshake-related fields type

* feature: updated handshake representations in templates

* feature: added handshake field to Swagger schema
2023-12-23 12:56:52 +01:00
Christoph Haas
2f79dd04c0 adopt github actions 2023-10-26 11:29:34 +02:00
Christoph Haas
e5ed9736b3 update docker build settings, move to new docker hub repository, use stable branch and major version tags 2023-10-26 11:22:58 +02:00
Christoph Haas
c8353b85ae Merge branch 'replace_ext_lib' into stable 2023-10-26 10:40:06 +02:00
Christoph Haas
6142031387 update gin 2023-10-26 10:39:01 +02:00
Christoph Haas
dd86d0ff49 replace inaccessible external lib 2023-10-26 10:31:29 +02:00
Christoph Haas
bdd426a679 populate peer device type (#170) 2023-10-26 10:20:08 +02:00
5 changed files with 99 additions and 21 deletions

View File

@ -61,6 +61,26 @@ const companyName = ref(WGPORTAL_SITE_COMPANY_NAME);
const wgVersion = ref(WGPORTAL_VERSION); const wgVersion = ref(WGPORTAL_VERSION);
const currentYear = ref(new Date().getFullYear()) const currentYear = ref(new Date().getFullYear())
const userDisplayName = computed(() => {
let displayName = "Unknown";
if (auth.IsAuthenticated) {
if (auth.User.Firstname === "" && auth.User.Lastname === "") {
displayName = auth.User.Identifier;
} else if (auth.User.Firstname === "" && auth.User.Lastname !== "") {
displayName = auth.User.Lastname;
} else if (auth.User.Firstname !== "" && auth.User.Lastname === "") {
displayName = auth.User.Firstname;
} else if (auth.User.Firstname !== "" && auth.User.Lastname !== "") {
displayName = auth.User.Firstname + " " + auth.User.Lastname;
}
}
// pad string to 20 characters so that the menu is always the same size on desktop
if (displayName.length < 20 && window.innerWidth > 992) {
displayName = displayName.padStart(20, "\u00A0");
}
return displayName;
})
</script> </script>
<template> <template>
@ -93,7 +113,7 @@ const currentYear = ref(new Date().getFullYear())
<div class="navbar-nav d-flex justify-content-end"> <div class="navbar-nav d-flex justify-content-end">
<div v-if="auth.IsAuthenticated" class="nav-item dropdown"> <div v-if="auth.IsAuthenticated" class="nav-item dropdown">
<a aria-expanded="false" aria-haspopup="true" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" <a aria-expanded="false" aria-haspopup="true" class="nav-link dropdown-toggle" data-bs-toggle="dropdown"
href="#" role="button">{{ auth.User.Firstname }} {{ auth.User.Lastname }}</a> href="#" role="button">{{ userDisplayName }}</a>
<div class="dropdown-menu"> <div class="dropdown-menu">
<RouterLink :to="{ name: 'profile' }" class="dropdown-item"><i class="fas fa-user"></i> {{ $t('menu.profile') }}</RouterLink> <RouterLink :to="{ name: 'profile' }" class="dropdown-item"><i class="fas fa-user"></i> {{ $t('menu.profile') }}</RouterLink>
<RouterLink :to="{ name: 'settings' }" class="dropdown-item" v-if="auth.IsAdmin || !settings.Setting('ApiAdminOnly')"><i class="fas fa-gears"></i> {{ $t('menu.settings') }}</RouterLink> <RouterLink :to="{ name: 'settings' }" class="dropdown-item" v-if="auth.IsAdmin || !settings.Setting('ApiAdminOnly')"><i class="fas fa-gears"></i> {{ $t('menu.settings') }}</RouterLink>

View File

@ -57,6 +57,52 @@
} }
} }
}, },
"/auth/login/{provider}/callback": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Authentication"
],
"summary": "Handle the OAuth callback.",
"operationId": "auth_handleOauthCallbackGet",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/model.LoginProviderInfo"
}
}
}
}
}
},
"/auth/login/{provider}/init": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Authentication"
],
"summary": "Initiate the OAuth login flow.",
"operationId": "auth_handleOauthInitiateGet",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/model.LoginProviderInfo"
}
}
}
}
}
},
"/auth/logout": { "/auth/logout": {
"post": { "post": {
"produces": [ "produces": [

View File

@ -383,6 +383,8 @@ definitions:
type: boolean type: boolean
MailLinkOnly: MailLinkOnly:
type: boolean type: boolean
MinPasswordLength:
type: integer
PersistentConfigSupported: PersistentConfigSupported:
type: boolean type: boolean
SelfProvisioning: SelfProvisioning:
@ -456,7 +458,22 @@ paths:
summary: Get all available audit entries. Ordered by timestamp. summary: Get all available audit entries. Ordered by timestamp.
tags: tags:
- Audit - Audit
/auth/{provider}/callback: /auth/login:
post:
operationId: auth_handleLoginPost
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/model.LoginProviderInfo'
type: array
summary: Get all available external login providers.
tags:
- Authentication
/auth/login/{provider}/callback:
get: get:
operationId: auth_handleOauthCallbackGet operationId: auth_handleOauthCallbackGet
produces: produces:
@ -471,7 +488,7 @@ paths:
summary: Handle the OAuth callback. summary: Handle the OAuth callback.
tags: tags:
- Authentication - Authentication
/auth/{provider}/init: /auth/login/{provider}/init:
get: get:
operationId: auth_handleOauthInitiateGet operationId: auth_handleOauthInitiateGet
produces: produces:
@ -486,21 +503,6 @@ paths:
summary: Initiate the OAuth login flow. summary: Initiate the OAuth login flow.
tags: tags:
- Authentication - Authentication
/auth/login:
post:
operationId: auth_handleLoginPost
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/model.LoginProviderInfo'
type: array
summary: Get all available external login providers.
tags:
- Authentication
/auth/logout: /auth/logout:
post: post:
operationId: auth_handleLogoutPost operationId: auth_handleLogoutPost

View File

@ -2,6 +2,7 @@ package handlers
import ( import (
"context" "context"
"log/slog"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
@ -132,7 +133,7 @@ func (e AuthEndpoint) handleSessionInfoGet() http.HandlerFunc {
// @Summary Initiate the OAuth login flow. // @Summary Initiate the OAuth login flow.
// @Produce json // @Produce json
// @Success 200 {object} []model.LoginProviderInfo // @Success 200 {object} []model.LoginProviderInfo
// @Router /auth/{provider}/init [get] // @Router /auth/login/{provider}/init [get]
func (e AuthEndpoint) handleOauthInitiateGet() http.HandlerFunc { func (e AuthEndpoint) handleOauthInitiateGet() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
currentSession := e.session.GetData(r.Context()) currentSession := e.session.GetData(r.Context())
@ -177,6 +178,8 @@ func (e AuthEndpoint) handleOauthInitiateGet() http.HandlerFunc {
authCodeUrl, state, nonce, err := e.authService.OauthLoginStep1(context.Background(), provider) authCodeUrl, state, nonce, err := e.authService.OauthLoginStep1(context.Background(), provider)
if err != nil { if err != nil {
slog.Debug("failed to create oauth auth code URL",
"provider", provider, "error", err)
if autoRedirect && e.isValidReturnUrl(returnTo) { if autoRedirect && e.isValidReturnUrl(returnTo) {
redirectToReturn() redirectToReturn()
} else { } else {
@ -211,7 +214,7 @@ func (e AuthEndpoint) handleOauthInitiateGet() http.HandlerFunc {
// @Summary Handle the OAuth callback. // @Summary Handle the OAuth callback.
// @Produce json // @Produce json
// @Success 200 {object} []model.LoginProviderInfo // @Success 200 {object} []model.LoginProviderInfo
// @Router /auth/{provider}/callback [get] // @Router /auth/login/{provider}/callback [get]
func (e AuthEndpoint) handleOauthCallbackGet() http.HandlerFunc { func (e AuthEndpoint) handleOauthCallbackGet() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
currentSession := e.session.GetData(r.Context()) currentSession := e.session.GetData(r.Context())
@ -249,6 +252,8 @@ func (e AuthEndpoint) handleOauthCallbackGet() http.HandlerFunc {
oauthState := request.Query(r, "state") oauthState := request.Query(r, "state")
if provider != currentSession.OauthProvider { if provider != currentSession.OauthProvider {
slog.Debug("invalid oauth provider in callback",
"expected", currentSession.OauthProvider, "got", provider, "state", oauthState)
if returnUrl != nil && e.isValidReturnUrl(returnUrl.String()) { if returnUrl != nil && e.isValidReturnUrl(returnUrl.String()) {
redirectToReturn() redirectToReturn()
} else { } else {
@ -258,6 +263,8 @@ func (e AuthEndpoint) handleOauthCallbackGet() http.HandlerFunc {
return return
} }
if oauthState != currentSession.OauthState { if oauthState != currentSession.OauthState {
slog.Debug("invalid oauth state in callback",
"expected", currentSession.OauthState, "got", oauthState, "provider", provider)
if returnUrl != nil && e.isValidReturnUrl(returnUrl.String()) { if returnUrl != nil && e.isValidReturnUrl(returnUrl.String()) {
redirectToReturn() redirectToReturn()
} else { } else {
@ -267,11 +274,13 @@ func (e AuthEndpoint) handleOauthCallbackGet() http.HandlerFunc {
return return
} }
loginCtx, cancel := context.WithTimeout(context.Background(), 1000*time.Second) loginCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) // avoid long waits
user, err := e.authService.OauthLoginStep2(loginCtx, provider, currentSession.OauthNonce, user, err := e.authService.OauthLoginStep2(loginCtx, provider, currentSession.OauthNonce,
oauthCode) oauthCode)
cancel() cancel()
if err != nil { if err != nil {
slog.Debug("failed to process oauth code",
"provider", provider, "state", oauthState, "error", err)
if returnUrl != nil && e.isValidReturnUrl(returnUrl.String()) { if returnUrl != nil && e.isValidReturnUrl(returnUrl.String()) {
redirectToReturn() redirectToReturn()
} else { } else {

View File

@ -136,6 +136,7 @@ func (p *Peer) OverwriteUserEditableFields(userPeer *Peer, cfg *config.Config) {
p.Interface.PublicKey = userPeer.Interface.PublicKey p.Interface.PublicKey = userPeer.Interface.PublicKey
p.Interface.PrivateKey = userPeer.Interface.PrivateKey p.Interface.PrivateKey = userPeer.Interface.PrivateKey
p.PresharedKey = userPeer.PresharedKey p.PresharedKey = userPeer.PresharedKey
p.Identifier = userPeer.Identifier
} }
p.Interface.Mtu = userPeer.Interface.Mtu p.Interface.Mtu = userPeer.Interface.Mtu
p.PersistentKeepalive = userPeer.PersistentKeepalive p.PersistentKeepalive = userPeer.PersistentKeepalive