Compare commits
10
Commits
c305b53f11
...
a0c57bd582
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0c57bd582 | ||
|
|
793904e942 | ||
|
|
fd12ba02fd | ||
|
|
e6cebed4ba | ||
|
|
08ba0130c1 | ||
|
|
4a7b3c91f0 | ||
|
|
f76d715cb9 | ||
|
|
af2d823705 | ||
|
|
16a38c972c | ||
|
|
4644a7d3f4 |
@@ -1,2 +1,3 @@
|
|||||||
config.json
|
config.json
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
token.json
|
||||||
|
|||||||
+38
-2
@@ -5,11 +5,20 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"astraltech.xyz/calendar/v2/caldav"
|
||||||
"astraltech.xyz/calendar/v2/webserver"
|
"astraltech.xyz/calendar/v2/webserver"
|
||||||
|
ics "github.com/arran4/golang-ical"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var TestTokenStore TokenStore
|
||||||
|
|
||||||
|
func SetAuth() {
|
||||||
|
TestTokenStore = TokenStore{filePath: "token.json"}
|
||||||
|
}
|
||||||
|
|
||||||
func generateState() (string, error) {
|
func generateState() (string, error) {
|
||||||
b := make([]byte, 32)
|
b := make([]byte, 32)
|
||||||
if _, err := rand.Read(b); err != nil {
|
if _, err := rand.Read(b); err != nil {
|
||||||
@@ -19,7 +28,34 @@ func generateState() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func HandlePasswordAuth(authData webserver.PasswordAuthData) bool {
|
func HandlePasswordAuth(authData webserver.PasswordAuthData) bool {
|
||||||
fmt.Printf("%s, %s\n", authData.Username, authData.Password)
|
calDavData := caldav.GetCalDAVData(authData.Username, authData.Password)
|
||||||
|
|
||||||
|
fmt.Printf("Users display name: %s\n", calDavData.DisplayName)
|
||||||
|
fmt.Printf("Calandar Count: %d\n", len(calDavData.Calandars))
|
||||||
|
for i := range len(calDavData.Calandars) {
|
||||||
|
calDavData.Calandars[i] = caldav.GetCalendarData(calDavData.Calandars[i], authData.Username, authData.Password)
|
||||||
|
|
||||||
|
fmt.Printf("Cal %d is named %s\n", i, calDavData.Calandars[i].DisplayName)
|
||||||
|
fmt.Printf("\tLink to cal: %s\n", calDavData.Calandars[i].Link)
|
||||||
|
fmt.Printf("\tEvent count: %d\n", len(calDavData.Calandars[i].Events))
|
||||||
|
for j := range len(calDavData.Calandars[i].Events) {
|
||||||
|
cal, err := ics.ParseCalendar(strings.NewReader(calDavData.Calandars[i].Events[j].CalData))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Print(err.Error())
|
||||||
|
}
|
||||||
|
events := cal.Events()
|
||||||
|
for k := range len(events) {
|
||||||
|
name := events[k].GetProperty(ics.ComponentProperty(ics.PropertySummary)).Value
|
||||||
|
start, _ := events[k].GetStartAt()
|
||||||
|
end, _ := events[k].GetEndAt()
|
||||||
|
time := end.Sub(start)
|
||||||
|
fmt.Printf("\t\tEvent name: %s\n", name)
|
||||||
|
fmt.Printf("\t\t\tEvent start date: %s\n", end.UTC().String())
|
||||||
|
fmt.Printf("\t\t\tEvent length date: %s\n", time.String())
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +84,7 @@ func HandleOAuth(authData webserver.OAuthAuthData) bool {
|
|||||||
SameSite: http.SameSiteLaxMode,
|
SameSite: http.SameSiteLaxMode,
|
||||||
})
|
})
|
||||||
|
|
||||||
url := OAuthConfigs[0].AuthCodeURL(
|
url := DefaultProvider.Config.AuthCodeURL(
|
||||||
state,
|
state,
|
||||||
oauth2.S256ChallengeOption(verifier),
|
oauth2.S256ChallengeOption(verifier),
|
||||||
)
|
)
|
||||||
|
|||||||
+25
-8
@@ -7,18 +7,28 @@ import (
|
|||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var OAuthConfigs []oauth2.Config
|
// This is all the user info that this script needs
|
||||||
|
type UserInfo struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
func CreateTestOAuth() {
|
type OAuthProvider struct {
|
||||||
|
Provider *oidc.Provider
|
||||||
|
Config oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
var Providers []OAuthProvider
|
||||||
|
var DefaultProvider *OAuthProvider // Assumed to be the last created provider
|
||||||
|
|
||||||
|
func CreateOAuthProvider(issuer string, clientID string) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
provider, err := oidc.NewProvider(ctx, issuer)
|
||||||
provider, err := oidc.NewProvider(ctx, "https://account.astraltech.xyz/application/o/stalwart/")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oauthConfig := oauth2.Config{
|
OAuthConfig := oauth2.Config{
|
||||||
ClientID: "stalwart-webui",
|
ClientID: clientID,
|
||||||
RedirectURL: "http://localhost:8080/callback",
|
RedirectURL: "http://localhost:8080/callback",
|
||||||
Endpoint: provider.Endpoint(),
|
Endpoint: provider.Endpoint(),
|
||||||
Scopes: []string{
|
Scopes: []string{
|
||||||
@@ -27,5 +37,12 @@ func CreateTestOAuth() {
|
|||||||
"email",
|
"email",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
OAuthConfigs = append(OAuthConfigs, oauthConfig)
|
|
||||||
|
newProvider := OAuthProvider{
|
||||||
|
Provider: provider,
|
||||||
|
Config: OAuthConfig,
|
||||||
|
}
|
||||||
|
Providers = append(Providers, newProvider)
|
||||||
|
DefaultProvider = &newProvider
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-2
@@ -1,11 +1,53 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// this is usually served at /callback
|
// this is usually served at /callback
|
||||||
func OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
func OAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Printf("Getting my OAuth callback")
|
state := r.FormValue("state")
|
||||||
|
code := r.FormValue("code")
|
||||||
|
|
||||||
|
stateCookie, err := r.Cookie("oauth_state")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Invalid state parameter", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if state != stateCookie.Value {
|
||||||
|
http.Error(w, "Invalid state parameter", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: "oauth_state",
|
||||||
|
Value: "",
|
||||||
|
Path: "/",
|
||||||
|
MaxAge: -1,
|
||||||
|
})
|
||||||
|
|
||||||
|
verifierCookie, err := r.Cookie("pkce_verifier")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Missing PKCE verifier", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
verifier := verifierCookie.Value
|
||||||
|
|
||||||
|
token, err := DefaultProvider.Config.Exchange(context.Background(), code, oauth2.VerifierOption(verifier))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the token securely
|
||||||
|
if err := TestTokenStore.Save(token); err != nil {
|
||||||
|
log.Printf("Failed to save token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/user", http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ const neededXML string = `
|
|||||||
</d:propfind>
|
</d:propfind>
|
||||||
`
|
`
|
||||||
|
|
||||||
var client *http.Client
|
var BuildInClient *http.Client
|
||||||
var serverURL string
|
var serverURL string
|
||||||
|
|
||||||
func InitDavClient(url string) {
|
func InitDavClient(url string) {
|
||||||
client = &http.Client{}
|
BuildInClient = &http.Client{}
|
||||||
serverURL = url
|
serverURL = url
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,6 @@ type SimpleCalDavData struct {
|
|||||||
Calandars []Calandar
|
Calandars []Calandar
|
||||||
}
|
}
|
||||||
|
|
||||||
// this makes stalwart assumptions
|
|
||||||
func GetCalDAVData(username string, password string) SimpleCalDavData {
|
func GetCalDAVData(username string, password string) SimpleCalDavData {
|
||||||
req, _ := http.NewRequest(
|
req, _ := http.NewRequest(
|
||||||
"PROPFIND",
|
"PROPFIND",
|
||||||
@@ -55,7 +54,7 @@ func GetCalDAVData(username string, password string) SimpleCalDavData {
|
|||||||
req.Header.Set("Depth", "1")
|
req.Header.Set("Depth", "1")
|
||||||
req.Header.Set("Content-Type", "application/xml")
|
req.Header.Set("Content-Type", "application/xml")
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := BuildInClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Print(err.Error())
|
fmt.Print(err.Error())
|
||||||
}
|
}
|
||||||
@@ -120,7 +119,7 @@ func GetCalendarData(cal Calandar, username string, password string) Calandar {
|
|||||||
req.Header.Set("Depth", "1")
|
req.Header.Set("Depth", "1")
|
||||||
req.Header.Set("Content-Type", "application/xml")
|
req.Header.Set("Content-Type", "application/xml")
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := BuildInClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Print(err.Error())
|
fmt.Print(err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"caldav_url:": "https://mx.example.com"
|
"caldav_url:": "https://mx.example.com",
|
||||||
|
"oauth_enabled": true,
|
||||||
|
"oauth_issuer": "https://sso.example.com/application/o/stalwart/",
|
||||||
|
"oauth_clientid": "stalwart-webui"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ module astraltech.xyz/calendar/v2
|
|||||||
go 1.26.1
|
go 1.26.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/arran4/golang-ical v0.3.5 // indirect
|
github.com/arran4/golang-ical v0.3.5
|
||||||
github.com/coreos/go-oidc/v3 v3.20.0 // indirect
|
github.com/coreos/go-oidc/v3 v3.20.0
|
||||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
github.com/go-jose/go-jose/v4 v4.1.4
|
||||||
golang.org/x/oauth2 v0.36.0 // indirect
|
golang.org/x/oauth2 v0.36.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"astraltech.xyz/calendar/v2/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
token, err := auth.TestTokenStore.Load()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Not authenticated", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client := auth.DefaultProvider.Config.Client(context.Background(), token)
|
||||||
|
|
||||||
|
resp, err := client.Get(auth.DefaultProvider.Provider.UserInfoEndpoint())
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to fetch user: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var usersInfo auth.UserInfo
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&usersInfo); err != nil {
|
||||||
|
http.Error(w, "Failed to decode response", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("The users email is %s\n", usersInfo.Email)
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ import (
|
|||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
URL string `json:"caldav_url"`
|
URL string `json:"caldav_url"`
|
||||||
|
OAuthEnabled bool `json:"oauth_enabled"`
|
||||||
|
OAuthIssuer string `json:"oauth_issuer"`
|
||||||
|
OAuthClientID string `json:"oauth_clientid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverConfig Config
|
var serverConfig Config
|
||||||
|
|||||||
+8
-1
@@ -7,8 +7,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
auth.CreateTestOAuth()
|
auth.SetAuth()
|
||||||
read_config()
|
read_config()
|
||||||
|
|
||||||
|
if serverConfig.OAuthEnabled {
|
||||||
|
auth.CreateOAuthProvider(serverConfig.OAuthIssuer, serverConfig.OAuthClientID)
|
||||||
|
}
|
||||||
|
|
||||||
caldav.InitDavClient(serverConfig.URL)
|
caldav.InitDavClient(serverConfig.URL)
|
||||||
|
|
||||||
webserver.ServeLoginPage("/login", webserver.CustomizableLoginData{
|
webserver.ServeLoginPage("/login", webserver.CustomizableLoginData{
|
||||||
@@ -16,6 +21,8 @@ func main() {
|
|||||||
AuthRequestFunction: auth.HandleAuthRequest,
|
AuthRequestFunction: auth.HandleAuthRequest,
|
||||||
})
|
})
|
||||||
webserver.ServeWebpage("/callback", auth.OAuthCallback)
|
webserver.ServeWebpage("/callback", auth.OAuthCallback)
|
||||||
|
webserver.ServeWebpage("/user", handleUser)
|
||||||
|
|
||||||
webserver.EnableLogoRoute()
|
webserver.EnableLogoRoute()
|
||||||
webserver.EnableStaticRoute()
|
webserver.EnableStaticRoute()
|
||||||
webserver.ServeWebserver()
|
webserver.ServeWebserver()
|
||||||
|
|||||||
Reference in New Issue
Block a user