Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/in4it/go-devops-platform v0.1.7
github.com/in4it/go-devops-platform v0.1.8
github.com/russellhaering/gosaml2 v0.10.0 // indirect
github.com/russellhaering/goxmldsig v1.5.0 // indirect
)
Expand Down
30 changes: 2 additions & 28 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopacket/gopacket v1.4.0 h1:cr1OlFpzksCkZHNO0eLjaSSOrMQnpPXg0j6qHIY3y2U=
github.com/gopacket/gopacket v1.4.0/go.mod h1:EpvsxINeehp5qj4YMKMLf2/dekdhKn2IIAO/ZOifS7o=
github.com/gopacket/gopacket v1.5.0 h1:9s9fcSUVKFlRV97B77Bq9XNV3ly2gvvsneFMQUGjc+M=
github.com/gopacket/gopacket v1.5.0/go.mod h1:i3NaGaqfoWKAr1+g7qxEdWsmfT+MXuWkAe9+THv8LME=
github.com/in4it/go-devops-platform v0.1.6 h1:vZkb5yztBhC5I4Mf+iLuOPNS6quOgIk+UiG6gQqjXyw=
github.com/in4it/go-devops-platform v0.1.6/go.mod h1:e8XwPp/yHjKTZquS5BPytSesZcAxq1dHFofHuVlRw8g=
github.com/in4it/go-devops-platform v0.1.7-0.20260210202834-8e3f0f785633 h1:NdIM+YkTu2mcVh5gGGnM8aToFUuN/H5So+1rEFc51Pg=
github.com/in4it/go-devops-platform v0.1.7-0.20260210202834-8e3f0f785633/go.mod h1:e8XwPp/yHjKTZquS5BPytSesZcAxq1dHFofHuVlRw8g=
github.com/in4it/go-devops-platform v0.1.7 h1:Bd5t84N9qI98ysQNcjZyzGZ6iloKXV+79x0NtmSh1eU=
github.com/in4it/go-devops-platform v0.1.7/go.mod h1:rCxubxHYzF6idIN7V4xED2ytXGdNbQAJZjeEPgQzZxw=
github.com/in4it/go-devops-platform v0.1.8 h1:OGGsfj7KTvrcyCG0Rqh2bHnlAbdRxr9/B56i6jecM+8=
github.com/in4it/go-devops-platform v0.1.8/go.mod h1:rCxubxHYzF6idIN7V4xED2ytXGdNbQAJZjeEPgQzZxw=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
Expand All @@ -33,8 +25,6 @@ github.com/mdlayher/netlink v1.8.0 h1:e7XNIYJKD7hUct3Px04RuIGJbBxy1/c4nX7D5Yyvvl
github.com/mdlayher/netlink v1.8.0/go.mod h1:UhgKXUlDQhzb09DrCl2GuRNEglHmhYoWAHid9HK3594=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/packetcap/go-pcap v0.0.0-20251012111502-b21daddd9e00 h1:mZeo53cf/lsCL4kj8QkmgXSRDK1caYmVI1aCq3Wi62o=
github.com/packetcap/go-pcap v0.0.0-20251012111502-b21daddd9e00/go.mod h1:1jryUz9E2ndKwZBNHzVhLMzS3WHO0fOKydYi9XWWu9w=
github.com/packetcap/go-pcap v0.0.0-20251215121130-f2cf9f991e7c h1:B5gWB1LB6OxpoXz+FsLUhQEcCAtMpajhBZ2K0X9KjfE=
github.com/packetcap/go-pcap v0.0.0-20251215121130-f2cf9f991e7c/go.mod h1:1jryUz9E2ndKwZBNHzVhLMzS3WHO0fOKydYi9XWWu9w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand All @@ -43,38 +33,22 @@ github.com/russellhaering/gosaml2 v0.10.0 h1:z7JTpKmC4JVG94tvSQz4lszUdKLt+uy5c6l
github.com/russellhaering/gosaml2 v0.10.0/go.mod h1:XLwI/5aWV4E2X9p+qj6LgRwiYGv2nh4YS6pQBGlQ0Cc=
github.com/russellhaering/goxmldsig v1.5.0 h1:AU2UkkYIUOTyZRbe08XMThaOCelArgvNfYapcmSjBNw=
github.com/russellhaering/goxmldsig v1.5.0/go.mod h1:x98CjQNFJcWfMxeOrMnMKg70lvDP6tE0nTaeUnjXDmk=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
2 changes: 1 addition & 1 deletion latest
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.1.14
v1.1.15
210 changes: 95 additions & 115 deletions webapp/src/Auth/AuthBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {useState} from 'react';
import { useState } from 'react';
import {
TextInput,
PasswordInput,
Paper,
Title,
Container,
Button,
Group,
Divider,
Text,
Alert,
} from '@mantine/core';
TextInput,
PasswordInput,
Paper,
Title,
Container,
Button,
Group,
Divider,
Text,
Alert,
} from '@mantine/core';
import classes from './AuthBanner.module.css';
import { useMutation, useQuery } from '@tanstack/react-query';
import axios from 'axios';
Expand Down Expand Up @@ -51,119 +51,99 @@ type LoginPassword = {


export function AuthBanner() {
const [login, setLogin] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [authError, setAuthError] = useState<string>("");
const {authInfo, setAuthInfo} = useAuthContext();
const [oidcRedirectError, setOidcRedirectError] = useState<string>("")
const [showMFAFactors, setShowMFAFactors] = useState<Array<string>>([])
const [factorResponse, setFactorResponse] = useState<FactorResponse>({name: "", code: ""})
const { error, isPending, data } = useQuery<any,any,AuthMethods>({
queryKey: ['authmethods'],
queryFn: () =>
fetch(AppSettings.url + '/authmethods')
const [login, setLogin] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [authError, setAuthError] = useState<string>("");
const { authInfo, setAuthInfo } = useAuthContext();
const [showMFAFactors, setShowMFAFactors] = useState<Array<string>>([])
const [factorResponse, setFactorResponse] = useState<FactorResponse>({ name: "", code: "" })
const { error, isPending, data } = useQuery<any, any, AuthMethods>({
queryKey: ['authmethods'],
queryFn: () =>
fetch(AppSettings.url + '/authmethods')
.then((res) =>
res.json(),
)
})

const authenticate = useMutation({
mutationFn: (loginPassword:LoginPassword) => {
setAuthError("")
return axios.post(AppSettings.url + '/auth', loginPassword)
},
onSuccess: (response) => {
const data = response.data as LoginResponse
if(data.mfaRequired) {
setShowMFAFactors(data.factors)
} else {
setAuthInfo({...authInfo, token: data.token})
}
},
onError: (error) => {
if(error.message.includes("status code 401")) {
setAuthError("Invalid credentials")
} else if(error.message.includes("status code 429")) {
setAuthError("too many attempts. Try again later")
} else {
setAuthError("Error: "+ error.message)
}
}
})
const oidcRedirect = useMutation({
mutationFn: (id:string) => {
return axios.get(AppSettings.url + '/authmethods/oidc/' + id)
},
onSuccess: (response) => {
const data = response.data as OIDCProvider
const redirectURI = data.redirectURI || ""
if(redirectURI === "") {
setOidcRedirectError("Could not redirect at this time: redirectURI is empty")
} else {
window.location.href = redirectURI
}

},
onError: (error) => {
setOidcRedirectError("Could not redirect at this time: "+ error.message)
}
})
const onClickOidcRedirect = (id:string) => {
oidcRedirect.mutate(id)
}
})

const captureEnter = (e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === "Enter") {
authenticate.mutate({login, password, factorResponse})
const authenticate = useMutation({
mutationFn: (loginPassword: LoginPassword) => {
setAuthError("")
return axios.post(AppSettings.url + '/auth', loginPassword)
},
onSuccess: (response) => {
const data = response.data as LoginResponse
if (data.mfaRequired) {
setShowMFAFactors(data.factors)
} else {
setAuthInfo({ ...authInfo, token: data.token })
}
},
onError: (error) => {
if (error.message.includes("status code 401")) {
setAuthError("Invalid credentials")
} else if (error.message.includes("status code 429")) {
setAuthError("too many attempts. Try again later")
} else {
setAuthError("Error: " + error.message)
}
}
})
const onClickOidcRedirect = (id: string) => {
window.location.href = AppSettings.url + '/authmethods/oidc/' + id + "/redirect"
}

const captureEnter = (e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === "Enter") {
authenticate.mutate({ login, password, factorResponse })
}
const alertIcon = <TbInfoCircle />

if (error) return 'An backend error has occurred: ' + error.message
}
const alertIcon = <TbInfoCircle />

const authMethodsButtons = data?.oidcProviders.map((oidcProvider:OIDCProvider) => (
<Container key={oidcProvider.id}><Button radius="xl" fullWidth={true} key={oidcProvider.id} onClick={() => onClickOidcRedirect
(oidcProvider.id)}>Login with {oidcProvider.name}</Button></Container>
))
if (error) return 'An backend error has occurred: ' + error.message

return (
<Container size={420} my={40}>
<Title ta="center" className={classes.title}>
VPN Server
</Title>
<AuthError />
<Paper withBorder shadow="md" p={30} mt={30} radius="md">
<Group grow mb="md" mt="md">
{oidcRedirectError === "" ? "" : <p>oidcRedirectError</p>}
{authMethodsButtons}
</Group>
{isPending || data?.localAuthDisabled ? null :
const authMethodsButtons = data?.oidcProviders.map((oidcProvider: OIDCProvider) => (
<Container key={oidcProvider.id}><Button radius="xl" fullWidth={true} key={oidcProvider.id} onClick={() => onClickOidcRedirect
(oidcProvider.id)}>Login with {oidcProvider.name}</Button></Container>
))

return (
<Container size={420} my={40}>
<Title ta="center" className={classes.title}>
VPN Server
</Title>
<AuthError />
<Paper withBorder shadow="md" p={30} mt={30} radius="md">
<Group grow mb="md" mt="md">
{authMethodsButtons}
</Group>
{isPending || data?.localAuthDisabled ? null :
<>
{data?.oidcProviders === undefined || data?.oidcProviders.length < 1 ? null :
<Divider label="Or continue with login" labelPosition="center" my="lg" />
}
{authError !== "" ?
<Alert variant="light" color="red" title="Error" icon={alertIcon}>{authError}</Alert>
:
null
}
{showMFAFactors.length > 0 ?
<MFAInput factors={showMFAFactors} setFactorResponse={setFactorResponse} captureEnter={captureEnter} />
:
<>
{data?.oidcProviders === undefined || data?.oidcProviders.length < 1 ? null :
<Divider label="Or continue with login" labelPosition="center" my="lg" />
}
{authError !== "" ?
<Alert variant="light" color="red" title="Error" icon={alertIcon}>{authError}</Alert>
:
null
}
{showMFAFactors.length > 0 ?
<MFAInput factors={showMFAFactors} setFactorResponse={setFactorResponse} captureEnter={captureEnter} />
:
<>
<TextInput label="Login" placeholder="Your username" required onChange={(event) => setLogin(event.currentTarget.value)} value={login} onKeyDown={(e) => captureEnter(e)} />
<PasswordInput label="Password" placeholder="Your password" required mt="md" onChange={(event) => setPassword(event.currentTarget.value)} value={password} onKeyDown={(e) => captureEnter(e)} />
</>
}
<Button fullWidth mt="xl" onClick={() => authenticate.mutate({login, password, factorResponse})}>
Sign in
</Button>
<Text size="xs" style={{marginTop: 20}}>By clicking 'Sign in', you accept the <a href="https://in4it.com/vpn-server-terms-conditions/" target="_blank">Terms & Conditions</a></Text>
<TextInput label="Login" placeholder="Your username" required onChange={(event) => setLogin(event.currentTarget.value)} value={login} onKeyDown={(e) => captureEnter(e)} />
<PasswordInput label="Password" placeholder="Your password" required mt="md" onChange={(event) => setPassword(event.currentTarget.value)} value={password} onKeyDown={(e) => captureEnter(e)} />
</>
}
<Button fullWidth mt="xl" onClick={() => authenticate.mutate({ login, password, factorResponse })}>
Sign in
</Button>
<Text size="xs" style={{ marginTop: 20 }}>By clicking 'Sign in', you accept the <a href="https://in4it.com/vpn-server-terms-conditions/" target="_blank">Terms & Conditions</a></Text>
</>
}

</Paper>
</Container>
);

</Paper>
</Container>
);

}
43 changes: 8 additions & 35 deletions webapp/src/Auth/OIDCAndSAMLRedirect.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,14 @@
import { useEffect, useState } from "react";
import { useEffect } from "react";
import { AppSettings } from "../Constants/Constants";
import axios from "axios";
import { useMutation } from "@tanstack/react-query";
import { useMatch } from "react-router-dom";

type AuthParams = {
loginType: string;
loginID: string;
};

export function OIDCAndSAMLRedirect() {
const [redirectError, setRedirectError] = useState<string>("")
const loginMatch = useMatch("/login/:loginType/:loginID");
const redirect = useMutation({
mutationFn: (authParams:AuthParams) => {
return axios.get(AppSettings.url + '/authmethods/'+authParams.loginType+'/' + authParams.loginID)
},
onSuccess: (response) => {
const data = response.data as OIDCProvider
const redirectURI = data.redirectURI || ""
if(redirectURI === "") {
setRedirectError("Could not redirect at this time: redirectURI is empty")
} else {
window.location.href = redirectURI
}

},
onError: (error) => {
setRedirectError("Could not redirect at this time: "+ error.message)
}
})
const loginMatch = useMatch("/login/:loginType/:loginID");

useEffect(() => {
if(loginMatch && loginMatch.params.loginType !== undefined && loginMatch.params.loginID !== undefined) {
redirect.mutate({loginType:loginMatch.params.loginType, loginID: loginMatch.params.loginID})
}
}, []);
if(redirectError !== "") return <p>RedirectError</p>
return (<></>)
useEffect(() => {
if (loginMatch && loginMatch.params.loginType !== undefined && loginMatch.params.loginID !== undefined) {
window.location.href = AppSettings.url + '/authmethods/' + loginMatch.params.loginType + '/' + loginMatch.params.loginID + "/redirect"
}
}, []);
return (<></>)
}
Loading