Skip to content

Commit eba56a2

Browse files
committed
Do not assign broadcast address when determining ip address, add start of client ui redesign
1 parent 9de8da4 commit eba56a2

File tree

12 files changed

+244
-137
lines changed

12 files changed

+244
-137
lines changed

adminui/frontend/postcss.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
export default {
1+
module.exports = {
22
plugins: {
33
tailwindcss: {},
44
autoprefixer: {},
55
},
6-
}
6+
}

internal/data/dhcp.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func getNextIP(subnet string) (string, error) {
102102
}
103103

104104
addr = incrementIP(addr, 1)
105-
if cidr.Contains(addr) {
105+
if cidr.Contains(addr) && !broadcastAddr(cidr).Equal(addr) {
106106
continue
107107
} else {
108108
addr = incrementIP(cidr.IP, 1)
@@ -115,3 +115,22 @@ func getNextIP(subnet string) (string, error) {
115115
}
116116

117117
}
118+
119+
// https://github.com/IBM/netaddr/blob/master/net_utils.go#L73
120+
func broadcastAddr(n *net.IPNet) net.IP {
121+
// The golang net package doesn't make it easy to calculate the broadcast address. :(
122+
var broadcast net.IP
123+
switch len(n.IP) {
124+
case 4:
125+
broadcast = net.ParseIP("0.0.0.0").To4()
126+
case 16:
127+
broadcast = net.ParseIP("::")
128+
default:
129+
panic("Bad value for size")
130+
}
131+
132+
for i := 0; i < len(n.IP); i++ {
133+
broadcast[i] = n.IP[i] | ^n.Mask[i]
134+
}
135+
return broadcast
136+
}

internal/mfaportal/authenticators/authenticators.go

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"log"
66
"net/http"
7+
"slices"
78
"sort"
89
"sync"
910

@@ -55,7 +56,7 @@ func EnableMethods(method ...types.MFA) {
5556
}
5657
}
5758

58-
func ReinitaliseMethods(firewall *router.Firewall, method ...types.MFA) ([]types.MFA, error) {
59+
func ReinitaliseMethods(method ...types.MFA) ([]types.MFA, error) {
5960
lck.Lock()
6061
defer lck.Unlock()
6162

@@ -64,7 +65,7 @@ func ReinitaliseMethods(firewall *router.Firewall, method ...types.MFA) ([]types
6465
var errRet error
6566
for _, m := range method {
6667
if a, ok := allMfa[m]; ok {
67-
err := a.Init(firewall)
68+
err := a.ReloadSettings()
6869
if err != nil {
6970
if errRet == nil {
7071
errRet = fmt.Errorf("%s failed to init: %s", m, err)
@@ -112,6 +113,7 @@ func GetAllEnabledMethods() (r []Authenticator) {
112113
return
113114
}
114115

116+
// GetAllAvaliableMethods returns All implemented authenticators in wag
115117
func GetAllAvaliableMethods() (r []Authenticator) {
116118
lck.RLock()
117119
defer lck.RUnlock()
@@ -133,69 +135,55 @@ func AddMFARoutes(mux *http.ServeMux, firewall *router.Firewall) error {
133135
lck.Lock()
134136
defer lck.Unlock()
135137

136-
for method, handler := range allMfa {
137-
mux.HandleFunc("GET /authorise/"+string(method)+"/", checkEnabled(handler, handler.AuthorisationAPI))
138-
mux.HandleFunc("POST /authorise/"+string(method)+"/", checkEnabled(handler, handler.AuthorisationAPI))
139-
mux.HandleFunc("GET /register_mfa/"+string(method)+"/", checkEnabled(handler, handler.RegistrationAPI))
140-
mux.HandleFunc("POST /register_mfa/"+string(method)+"/", checkEnabled(handler, handler.RegistrationAPI))
141-
142-
}
143-
144138
enabledMethods, err := data.GetEnabledAuthenicationMethods()
145139
if err != nil {
146140
return err
147141
}
148142

149-
for _, method := range enabledMethods {
150-
err := allMfa[types.MFA(method)].Init(firewall)
143+
for method, handler := range allMfa {
144+
prefix := "/api/" + string(method)
145+
r, err := handler.Routes(firewall, slices.Contains(enabledMethods, string(method)))
151146
if err != nil {
152147
log.Println("failed to initialise method: ", method, "err: ", err)
153148
continue
154149
}
155-
allMfa[types.MFA(method)].Enable()
150+
151+
mux.Handle(prefix, http.StripPrefix(prefix, checkEnabled(r, allMfa[method])))
156152
}
157153

158154
return nil
159155
}
160156

161-
func checkEnabled(a Authenticator, f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
162-
return func(w http.ResponseWriter, r *http.Request) {
157+
type enabled struct {
158+
next http.Handler
159+
auth Authenticator
160+
}
163161

164-
if !a.IsEnabled() {
165-
http.NotFound(w, r)
166-
return
167-
}
162+
func (d *enabled) ServeHTTP(w http.ResponseWriter, r *http.Request) {
163+
d.next.ServeHTTP(w, r)
164+
}
168165

169-
f(w, r)
166+
func checkEnabled(next http.Handler, auth Authenticator) http.Handler {
167+
return &enabled{
168+
next: next,
169+
auth: auth,
170170
}
171171
}
172172

173173
type Authenticator interface {
174-
Init(fw *router.Firewall) error
175-
176174
IsEnabled() bool
175+
177176
Enable()
178177
Disable()
179178

179+
ReloadSettings() error
180+
180181
Type() string
181182

182183
//FriendlyName is the name that is displayed in the MFA selection table
183184
FriendlyName() string
184185

185-
//LogoutPath returns the redirection path that deauthenticates selected mfa method (mostly just "/" unless it's externally connected to something)
186-
LogoutPath() string
187-
188-
//RegistrationAPI automatically added under /register_mfa/<mfa_method_name>
189-
RegistrationAPI(w http.ResponseWriter, r *http.Request)
190-
191-
//AuthorisationAPI automatically added under /authorise/<mfa_method_name>
192-
AuthorisationAPI(w http.ResponseWriter, r *http.Request)
193-
194-
//MFAPromptUI is executed in /authorise/ path to display UI when user browses to that path
195-
MFAPromptUI(w http.ResponseWriter, r *http.Request, username, ip string)
196-
197-
//RegistrationUI is executed in /register_mfa/ path to show the UI for registration
198-
RegistrationUI(w http.ResponseWriter, r *http.Request, username, ip string)
186+
Routes(fw *router.Firewall, initiallyEnabled bool) (*http.ServeMux, error)
199187
}
200188

201189
func StringsToMFA(methods []string) (ret []types.MFA) {

internal/mfaportal/authenticators/oidc.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,7 @@ type Oidc struct {
3636
fw *router.Firewall
3737
}
3838

39-
func (o *Oidc) LogoutPath() string {
40-
return o.provider.GetEndSessionEndpoint()
41-
}
42-
43-
func (o *Oidc) Init(fw *router.Firewall) error {
39+
func (o *Oidc) Routes(fw *router.Firewall, initiallyEnabled bool) error {
4440

4541
o.fw = fw
4642

internal/mfaportal/dtos.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package mfaportal
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
)
7+
8+
type MFAMethod struct {
9+
Method string `json:"method"`
10+
FriendlyName string `json:"friendly_name"`
11+
}
12+
13+
type UserInfoDTO struct {
14+
Locked bool `json:"is_locked"`
15+
HelpMail string `json:"helpmail"`
16+
AvailableMfaMethods []MFAMethod `json:"available_mfa_methods"`
17+
Registered bool `json:"has_registered"`
18+
Username string `json:"username"`
19+
Authorised bool `json:"is_authorized"`
20+
}
21+
22+
type StatusDTO struct {
23+
IsAuthorised bool
24+
25+
MFA []string
26+
Public []string
27+
Deny []string
28+
}
29+
30+
type GenericResponseDTO struct {
31+
Message string `json:"message"`
32+
Success bool `json:"success"`
33+
}
34+
35+
func (mp *MfaPortal) respond(err error, w http.ResponseWriter) {
36+
37+
var resp GenericResponseDTO
38+
resp.Success = true
39+
w.Header().Set("content-type", "application/json")
40+
resp.Message = "OK"
41+
if err != nil {
42+
resp.Success = false
43+
resp.Message = err.Error()
44+
}
45+
46+
json.NewEncoder(w).Encode(resp)
47+
48+
}
49+
50+
func (mp *MfaPortal) respondSuccess(err error, success string, w http.ResponseWriter) {
51+
52+
var resp GenericResponseDTO
53+
resp.Success = true
54+
w.Header().Set("content-type", "application/json")
55+
resp.Message = success
56+
if err != nil {
57+
resp.Success = false
58+
resp.Message = err.Error()
59+
}
60+
61+
json.NewEncoder(w).Encode(resp)
62+
63+
}

internal/mfaportal/middleware.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package mfaportal
2+
3+
import (
4+
"context"
5+
"log"
6+
"net/http"
7+
8+
"github.com/NHAS/wag/internal/router"
9+
"github.com/NHAS/wag/internal/users"
10+
"github.com/NHAS/wag/internal/utils"
11+
)
12+
13+
type userChecks struct {
14+
next http.Handler
15+
f *router.Firewall
16+
}
17+
18+
func (d *userChecks) ServeHTTP(w http.ResponseWriter, r *http.Request) {
19+
clientTunnelIp := utils.GetIPFromRequest(r)
20+
21+
userObj, err := users.GetUserFromAddress(clientTunnelIp)
22+
if err != nil {
23+
http.Error(w, "Server Error", http.StatusInternalServerError)
24+
log.Printf("device with unknown ip %q (%q, xff %q) used vpn endpoint CHECK CONFIG", clientTunnelIp, r.Host, r.Header.Get("X-forwarded-for"))
25+
return
26+
}
27+
28+
ctx := context.WithValue(r.Context(), users.UserContextKey, userObj)
29+
30+
if clientTunnelIp != nil {
31+
ctx = context.WithValue(ctx, authContext, d.f.IsAuthed(clientTunnelIp.String()))
32+
}
33+
// Create new request with updated context
34+
r = r.WithContext(ctx)
35+
36+
d.next.ServeHTTP(w, r)
37+
}
38+
39+
func fetchState(f http.Handler, firewall *router.Firewall) http.Handler {
40+
return &userChecks{
41+
next: f,
42+
f: firewall,
43+
}
44+
}
45+
46+
type authContextKey string
47+
48+
// Define context key for user
49+
const authContext authContextKey = "authed"
50+
51+
func Authed(ctx context.Context) bool {
52+
authed, ok := ctx.Value(authContext).(bool)
53+
return ok && authed
54+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import axios from "axios";
22

33
export const client = axios.create();
4-
export const verifyEndpoint = "/api/mfa/verify";
4+
export const verifyEndpoint = "/api/verify";
55

66
export * from "./types";
77
export * from "./totp";
88
export * from "./webauthn";
9-
export * from "./pam";
9+
export * from "./pam";
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { client, type UserInfoDTO } from ".";
22

33
export function apiGetInfo(): Promise<UserInfoDTO> {
4-
return client.get("/api/info").then((res) => res.data);
4+
return client.get("/api/userinfo").then((res) => res.data);
55
}

internal/mfaportal/resources/frontend/src/api/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ export interface MFAMethod {
33
method: string;
44
}
55

6+
export interface MFAMethod {
7+
method: string
8+
friendly_name: string
9+
}
10+
611
export interface UserInfoDTO {
712
has_registered: boolean;
813
available_mfa_methods: MFAMethod[];

internal/mfaportal/statemachine.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (mp *MfaPortal) oidcChanges(_ string, _ data.OIDC, _ data.OIDC, et data.Eve
6464
}
6565

6666
if slices.Contains(methods, string(types.Oidc)) {
67-
_, err := authenticators.ReinitaliseMethods(mp.firewall, types.Oidc)
67+
_, err := authenticators.ReinitaliseMethods(types.Oidc)
6868

6969
return err
7070
}
@@ -84,7 +84,7 @@ func (mp *MfaPortal) domainChanged(_ string, _, _ data.WebserverConfiguration, e
8484
}
8585

8686
if slices.Contains(methods, string(types.Oidc)) {
87-
_, err := authenticators.ReinitaliseMethods(mp.firewall, types.Oidc)
87+
_, err := authenticators.ReinitaliseMethods(types.Oidc)
8888

8989
return err
9090
}
@@ -101,14 +101,14 @@ func (mp *MfaPortal) enabledMethodsChanged(_ string, current, previous []string,
101101
case data.CREATED:
102102
var initdMethods []types.MFA
103103

104-
initdMethods, err = authenticators.ReinitaliseMethods(mp.firewall, authenticators.StringsToMFA(current)...)
104+
initdMethods, err = authenticators.ReinitaliseMethods(authenticators.StringsToMFA(current)...)
105105
authenticators.EnableMethods(initdMethods...)
106106

107107
case data.MODIFIED:
108108
var initdMethods []types.MFA
109109

110110
authenticators.DisableMethods(authenticators.StringsToMFA(previous)...)
111-
initdMethods, err = authenticators.ReinitaliseMethods(mp.firewall, authenticators.StringsToMFA(current)...)
111+
initdMethods, err = authenticators.ReinitaliseMethods(authenticators.StringsToMFA(current)...)
112112

113113
authenticators.EnableMethods(initdMethods...)
114114
}
@@ -122,7 +122,7 @@ func (mp *MfaPortal) issuerKeyChanged(_ string, _, _ string, et data.EventType)
122122
case data.DELETED:
123123
authenticators.DisableMethods(types.Totp, types.Webauthn)
124124
case data.CREATED, data.MODIFIED:
125-
_, err := authenticators.ReinitaliseMethods(mp.firewall, types.Totp, types.Webauthn)
125+
_, err := authenticators.ReinitaliseMethods(types.Totp, types.Webauthn)
126126
return err
127127
}
128128

0 commit comments

Comments
 (0)