Compare commits

...
10 Commits
Author SHA1 Message Date
gawells a0c57bd582 create new OAuth provider from settings 2026-08-06 20:19:55 -04:00
gawells 793904e942 setup new provider endpoint creation 2026-08-06 20:19:11 -04:00
gawells fd12ba02fd new provider generation 2026-08-06 20:17:22 -04:00
gawells e6cebed4ba move calendar endpoint into new file 2026-08-06 20:16:55 -04:00
gawells 08ba0130c1 add in config for oauth endpoint 2026-08-06 20:16:10 -04:00
gawells 4a7b3c91f0 im sure I did something 2026-08-06 19:55:51 -04:00
gawells f76d715cb9 get basic information about the user 2026-08-06 19:38:04 -04:00
gawells af2d823705 readd in password auth 2026-08-06 14:54:20 -04:00
gawells 16a38c972c get token from OAuth provider 2026-08-06 14:46:08 -04:00
gawells 4644a7d3f4 Handle state in OAuth client 2026-08-06 14:37:53 -04:00
10 changed files with 167 additions and 24 deletions
+1
View File
@@ -1,2 +1,3 @@
config.json
.DS_Store
token.json
+38 -2
View File
@@ -5,11 +5,20 @@ import (
"encoding/base64"
"fmt"
"net/http"
"strings"
"astraltech.xyz/calendar/v2/caldav"
"astraltech.xyz/calendar/v2/webserver"
ics "github.com/arran4/golang-ical"
"golang.org/x/oauth2"
)
var TestTokenStore TokenStore
func SetAuth() {
TestTokenStore = TokenStore{filePath: "token.json"}
}
func generateState() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
@@ -19,7 +28,34 @@ func generateState() (string, error) {
}
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
}
@@ -48,7 +84,7 @@ func HandleOAuth(authData webserver.OAuthAuthData) bool {
SameSite: http.SameSiteLaxMode,
})
url := OAuthConfigs[0].AuthCodeURL(
url := DefaultProvider.Config.AuthCodeURL(
state,
oauth2.S256ChallengeOption(verifier),
)
+25 -8
View File
@@ -7,18 +7,28 @@ import (
"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()
provider, err := oidc.NewProvider(ctx, "https://account.astraltech.xyz/application/o/stalwart/")
provider, err := oidc.NewProvider(ctx, issuer)
if err != nil {
panic(err)
return err
}
oauthConfig := oauth2.Config{
ClientID: "stalwart-webui",
OAuthConfig := oauth2.Config{
ClientID: clientID,
RedirectURL: "http://localhost:8080/callback",
Endpoint: provider.Endpoint(),
Scopes: []string{
@@ -27,5 +37,12 @@ func CreateTestOAuth() {
"email",
},
}
OAuthConfigs = append(OAuthConfigs, oauthConfig)
newProvider := OAuthProvider{
Provider: provider,
Config: OAuthConfig,
}
Providers = append(Providers, newProvider)
DefaultProvider = &newProvider
return nil
}
+44 -2
View File
@@ -1,11 +1,53 @@
package auth
import (
"fmt"
"context"
"log"
"net/http"
"golang.org/x/oauth2"
)
// this is usually served at /callback
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)
}
+4 -5
View File
@@ -20,11 +20,11 @@ const neededXML string = `
</d:propfind>
`
var client *http.Client
var BuildInClient *http.Client
var serverURL string
func InitDavClient(url string) {
client = &http.Client{}
BuildInClient = &http.Client{}
serverURL = url
}
@@ -43,7 +43,6 @@ type SimpleCalDavData struct {
Calandars []Calandar
}
// this makes stalwart assumptions
func GetCalDAVData(username string, password string) SimpleCalDavData {
req, _ := http.NewRequest(
"PROPFIND",
@@ -55,7 +54,7 @@ func GetCalDAVData(username string, password string) SimpleCalDavData {
req.Header.Set("Depth", "1")
req.Header.Set("Content-Type", "application/xml")
resp, err := client.Do(req)
resp, err := BuildInClient.Do(req)
if err != nil {
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("Content-Type", "application/xml")
resp, err := client.Do(req)
resp, err := BuildInClient.Do(req)
if err != nil {
fmt.Print(err.Error())
}
+4 -1
View File
@@ -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"
}
+4 -4
View File
@@ -3,8 +3,8 @@ module astraltech.xyz/calendar/v2
go 1.26.1
require (
github.com/arran4/golang-ical v0.3.5 // indirect
github.com/coreos/go-oidc/v3 v3.20.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
github.com/arran4/golang-ical v0.3.5
github.com/coreos/go-oidc/v3 v3.20.0
github.com/go-jose/go-jose/v4 v4.1.4
golang.org/x/oauth2 v0.36.0
)
+35
View File
@@ -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)
}
+3
View File
@@ -8,6 +8,9 @@ import (
type Config struct {
URL string `json:"caldav_url"`
OAuthEnabled bool `json:"oauth_enabled"`
OAuthIssuer string `json:"oauth_issuer"`
OAuthClientID string `json:"oauth_clientid"`
}
var serverConfig Config
+8 -1
View File
@@ -7,8 +7,13 @@ import (
)
func main() {
auth.CreateTestOAuth()
auth.SetAuth()
read_config()
if serverConfig.OAuthEnabled {
auth.CreateOAuthProvider(serverConfig.OAuthIssuer, serverConfig.OAuthClientID)
}
caldav.InitDavClient(serverConfig.URL)
webserver.ServeLoginPage("/login", webserver.CustomizableLoginData{
@@ -16,6 +21,8 @@ func main() {
AuthRequestFunction: auth.HandleAuthRequest,
})
webserver.ServeWebpage("/callback", auth.OAuthCallback)
webserver.ServeWebpage("/user", handleUser)
webserver.EnableLogoRoute()
webserver.EnableStaticRoute()
webserver.ServeWebserver()