Compare commits

..

No commits in common. "oidc-v0.7.12" and "main" have entirely different histories.

9 changed files with 22 additions and 389 deletions

View file

@ -1,9 +1,8 @@
FROM golang:1.24.2-alpine3.21 AS builder
ARG VERSION
WORKDIR /app
COPY . /app
RUN CGO_ENABLED=0 go build -ldflags="-s -w -X github.com/glanceapp/glance/internal/glance.buildVersion=$VERSION" .
RUN CGO_ENABLED=0 go build .
FROM alpine:3.21

5
go.mod
View file

@ -3,13 +3,10 @@ module github.com/glanceapp/glance
go 1.24.2
require (
github.com/coreos/go-oidc/v3 v3.13.0
github.com/fsnotify/fsnotify v1.9.0
github.com/google/uuid v1.6.0
github.com/mmcdole/gofeed v1.3.0
github.com/shirou/gopsutil/v4 v4.25.3
github.com/tidwall/gjson v1.18.0
golang.org/x/oauth2 v0.28.0
golang.org/x/text v0.24.0
gopkg.in/yaml.v3 v3.0.1
)
@ -18,7 +15,6 @@ require (
github.com/PuerkitoBio/goquery v1.10.2 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/ebitengine/purego v0.8.2 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
@ -31,7 +27,6 @@ require (
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.32.0 // indirect
)

30
go.sum
View file

@ -1,29 +1,29 @@
github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU=
github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY=
github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/coreos/go-oidc/v3 v3.13.0 h1:M66zd0pcc5VxvBNM4pB331Wrsanby+QomQYjN8HamW8=
github.com/coreos/go-oidc/v3 v3.13.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4=
@ -39,6 +39,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
github.com/shirou/gopsutil/v4 v4.25.3 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE=
github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -52,8 +54,12 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@ -65,8 +71,6 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@ -81,10 +85,10 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -106,6 +110,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
@ -126,6 +132,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View file

@ -1,247 +0,0 @@
package glance
import (
"context"
"errors"
"log/slog"
"net/http"
"strings"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2"
)
type GlanceAuth struct {
OidcProvider *oidc.Provider
oAuth2Config oauth2.Config
OidcVefifier *oidc.IDTokenVerifier
cookieName string
https bool
}
type AuthenticatationResult struct {
IDToken string
RefreshToken string
IDTokenClaims *Claims
}
type Claims struct {
Issuer string `json:"iss"`
Audience string `json:"aud"`
IssuedAt UnixTime `json:"iat"`
Expiration UnixTime `json:"exp"`
}
func CreateGlanceAuth(ctx context.Context, config *config) (*GlanceAuth, error) {
provider, err := oidc.NewProvider(ctx, config.Authentication.Issuer)
if err != nil {
return nil, err
}
verifier := provider.Verifier(&oidc.Config{
ClientID: config.Authentication.ClientId,
})
scopes := []string{oidc.ScopeOpenID}
scopes = append(scopes, strings.Split(config.Authentication.Scopes, " ")...)
return &GlanceAuth{
OidcProvider: provider,
oAuth2Config: oauth2.Config{
ClientID: config.Authentication.ClientId,
ClientSecret: config.Authentication.ClientSecret,
RedirectURL: config.Server.BaseURL + "/auth/resp",
// Discovery returns the OAuth2 endpoints.
Endpoint: provider.Endpoint(),
// "openid" is a required scope for OpenID Connect flows.
Scopes: scopes,
},
OidcVefifier: verifier,
cookieName: config.Authentication.CookieName,
https: strings.HasPrefix(config.Server.BaseURL, "https://") || config.Server.UsesHttps,
}, nil
}
func (fw *GlanceAuth) VerifyToken(ctx context.Context, oauth2Token *oauth2.Token) (AuthenticatationResult, error) {
var result AuthenticatationResult
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
return result, errors.New("No id_token field in oauth2 token")
}
idToken, err := fw.OidcVefifier.Verify(ctx, rawIDToken)
if err != nil {
return result, err
}
result = AuthenticatationResult{
IDToken: rawIDToken,
RefreshToken: oauth2Token.RefreshToken,
IDTokenClaims: new(Claims),
}
if err := idToken.Claims(&result.IDTokenClaims); err != nil {
return result, err
}
return result, nil
}
func (fw *GlanceAuth) RefreshToken(ctx context.Context, refreshToken string) (*AuthenticatationResult, error) {
var result AuthenticatationResult
tokenSource := fw.oAuth2Config.TokenSource(ctx, &oauth2.Token{RefreshToken: refreshToken})
oauth2Token, err := tokenSource.Token()
if err != nil {
return &result, err
}
result, err = fw.VerifyToken(ctx, oauth2Token)
if err != nil {
return &result, err
}
return &result, nil
}
func getBaseCookie() *http.Cookie {
return &http.Cookie{
Path: "/",
HttpOnly: true,
Secure: true,
}
}
func (fw *GlanceAuth) MakeAuthCookie(authResult *AuthenticatationResult) *http.Cookie {
cookie := getBaseCookie()
cookie.Name = fw.cookieName
cookie.Value = authResult.IDToken
cookie.Expires = time.Now().Local().Add(time.Hour * 24)
return cookie
}
func (fw *GlanceAuth) GetAuthCookie(r *http.Request) (*http.Cookie, error) {
return r.Cookie(fw.cookieName)
}
func (fw *GlanceAuth) ClearAuthCookie() *http.Cookie {
cookie := getBaseCookie()
cookie.Name = fw.cookieName
cookie.Expires = time.Now().Local().Add(time.Hour * -1)
return cookie
}
func (fw *GlanceAuth) MakeRefreshAuthCookie(authResult *AuthenticatationResult) *http.Cookie {
cookie := getBaseCookie()
cookie.Name = fw.cookieName + "-refresh"
cookie.Value = authResult.RefreshToken
cookie.Expires = time.Now().Local().Add(time.Hour * 24)
return cookie
}
func (fw *GlanceAuth) GetRefreshAuthCookie(r *http.Request) (*http.Cookie, error) {
return r.Cookie(fw.cookieName + "-refresh")
}
func (fw *GlanceAuth) ClearRefreshAuthCookie() *http.Cookie {
cookie := getBaseCookie()
cookie.Name = fw.cookieName + "-refresh"
cookie.Expires = time.Now().Local().Add(time.Hour * -1)
return cookie
}
func (fw *GlanceAuth) HandleAuthentication(ctx context.Context, code string, host string) (*AuthenticatationResult, error) {
var result AuthenticatationResult
oauth := fw.OAuthConfig(host)
oauth2Token, err := oauth.Exchange(ctx, code)
if err != nil {
slog.Error(err.Error())
return &result, err
}
result, err = fw.VerifyToken(ctx, oauth2Token)
if err != nil {
slog.Error(err.Error())
return &result, err
}
slog.Debug("Authentication was succesful.")
return &result, nil
}
func (fw *GlanceAuth) IsAuthenticated(context context.Context, w http.ResponseWriter, r *http.Request) (*Claims, error) {
var claims Claims
// Check if we have an Auth cookie
cookie, err := fw.GetAuthCookie(r)
if err != nil {
return &claims, err
}
// check if the token is valid
idToken, err := fw.OidcVefifier.Verify(context, cookie.Value)
switch {
case err == nil: // Token is valid
slog.Debug("Received valid token.")
claims = Claims{}
if err := idToken.Claims(&claims); err != nil {
slog.Error(err.Error())
return &claims, err
}
return &claims, nil
case strings.Contains(err.Error(), "expired"): // Token is expired
slog.Debug("Received expired token, trying to refesh it.")
refreshCookie, err := fw.GetRefreshAuthCookie(r)
if err != nil {
slog.Error(err.Error())
return &claims, err
}
result, err := fw.RefreshToken(context, refreshCookie.Value)
if err != nil {
slog.Error(err.Error())
return &claims, err
}
http.SetCookie(w, fw.MakeAuthCookie(result))
if len(result.RefreshToken) > 0 { // Do we have an refresh token?
http.SetCookie(w, fw.MakeRefreshAuthCookie(result))
}
return result.IDTokenClaims, nil
default:
slog.Error(err.Error())
return &claims, err
}
}
func (fw *GlanceAuth) OAuthConfig(host string) oauth2.Config {
var proto string
if fw.https {
proto = "https://"
} else {
proto = "http://"
}
return oauth2.Config{
ClientID: fw.oAuth2Config.ClientID,
ClientSecret: fw.oAuth2Config.ClientSecret,
RedirectURL: proto + host + "/auth/resp",
Endpoint: fw.oAuth2Config.Endpoint,
Scopes: fw.oAuth2Config.Scopes,
}
}

View file

@ -23,7 +23,6 @@ type config struct {
Port uint16 `yaml:"port"`
AssetsPath string `yaml:"assets-path"`
BaseURL string `yaml:"base-url"`
UsesHttps bool `yaml:"uses-https"`
StartedAt time.Time `yaml:"-"` // used in custom css file
} `yaml:"server"`
@ -50,14 +49,6 @@ type config struct {
FaviconURL string `yaml:"favicon-url"`
} `yaml:"branding"`
Authentication struct {
Issuer string `yaml:"issuer"`
ClientId string `yaml:"client-id"`
ClientSecret string `yaml:"client-secret"`
Scopes string `yaml:"scopes"`
CookieName string `yaml:"cookie-name"`
}
Pages []page `yaml:"pages"`
}
@ -105,10 +96,6 @@ func newConfigFromYAML(contents []byte) (*config, error) {
}
}
if config.Authentication.CookieName == "" {
config.Authentication.CookieName = "glanceauth"
}
return config, nil
}

View file

@ -6,15 +6,12 @@ import (
"fmt"
"html/template"
"log"
"log/slog"
"net/http"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/google/uuid"
)
var (
@ -27,26 +24,15 @@ type application struct {
Version string
Config config
ParsedThemeStyle template.HTML
Auth *GlanceAuth
slugToPage map[string]*page
widgetByID map[uint64]widget
}
func newApplication(config *config) (*application, error) {
var auth *GlanceAuth
if config.Authentication.Issuer != "" {
var err error
auth, err = CreateGlanceAuth(context.Background(), config)
if err != nil {
return nil, err
}
}
app := &application{
Version: buildVersion,
Config: *config,
Auth: auth,
slugToPage: make(map[string]*page),
widgetByID: make(map[uint64]widget),
}
@ -144,10 +130,6 @@ type pageTemplateData struct {
}
func (a *application) handlePageRequest(w http.ResponseWriter, r *http.Request) {
if a.handleAuth(w, r, true) {
return
}
page, exists := a.slugToPage[r.PathValue("page")]
if !exists {
@ -172,10 +154,6 @@ func (a *application) handlePageRequest(w http.ResponseWriter, r *http.Request)
}
func (a *application) handlePageContentRequest(w http.ResponseWriter, r *http.Request) {
if a.handleAuth(w, r, false) {
return
}
page, exists := a.slugToPage[r.PathValue("page")]
if !exists {
@ -214,10 +192,6 @@ func (a *application) handleNotFound(w http.ResponseWriter, _ *http.Request) {
}
func (a *application) handleWidgetRequest(w http.ResponseWriter, r *http.Request) {
if a.handleAuth(w, r, false) {
return
}
widgetValue := r.PathValue("widget")
widgetID, err := strconv.ParseUint(widgetValue, 10, 64)
@ -236,43 +210,6 @@ func (a *application) handleWidgetRequest(w http.ResponseWriter, r *http.Request
widget.handleRequest(w, r)
}
func (a *application) handleOidcCallback(w http.ResponseWriter, r *http.Request) {
authResult, err := a.Auth.HandleAuthentication(r.Context(), r.URL.Query().Get("code"), r.Host)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.SetCookie(w, a.Auth.MakeAuthCookie(authResult))
if len(authResult.RefreshToken) > 0 {
http.SetCookie(w, a.Auth.MakeRefreshAuthCookie(authResult))
}
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
}
func (a *application) handleAuth(w http.ResponseWriter, r *http.Request, allowRedir bool) bool {
if a.Auth == nil {
return false
}
_, err := a.Auth.IsAuthenticated(r.Context(), w, r)
if err != nil {
if allowRedir {
slog.Debug("IsAuthenticated failed, initiating login flow")
http.SetCookie(w, a.Auth.ClearAuthCookie())
http.SetCookie(w, a.Auth.ClearRefreshAuthCookie())
state := uuid.New().String()
oauth := a.Auth.OAuthConfig(r.Host)
http.Redirect(w, r, oauth.AuthCodeURL(state), http.StatusTemporaryRedirect)
} else {
w.WriteHeader(http.StatusUnauthorized)
}
return true
}
return false
}
func (a *application) AssetPath(asset string) string {
return a.Config.Server.BaseURL + "/static/" + staticFSHash + "/" + asset
}
@ -291,10 +228,6 @@ func (a *application) server() (func() error, func() error) {
w.WriteHeader(http.StatusOK)
})
if a.Auth != nil {
mux.HandleFunc("GET /auth/resp", a.handleOidcCallback)
}
mux.Handle(
fmt.Sprintf("GET /static/%s/{path...}", staticFSHash),
http.StripPrefix("/static/"+staticFSHash, fileServerWithCache(http.FS(staticFS), 24*time.Hour)),

View file

@ -72,7 +72,7 @@
<footer class="footer flex items-center flex-column">
{{ if eq "" .App.Config.Branding.CustomFooter }}
<div>
<a class="size-h3" href="https://github.com/glanceapp/glance" target="_blank" rel="noreferrer">Glance</a> {{ if ne "dev" .App.Version }}<a class="visited-indicator" title="Release notes" href="https://code.thetadev.de/ThetaDev/glance/releases/tag/{{ .App.Version }}" target="_blank" rel="noreferrer">{{ .App.Version }}</a>{{ else }}({{ .App.Version }}){{ end }}
<a class="size-h3" href="https://github.com/glanceapp/glance" target="_blank" rel="noreferrer">Glance</a> {{ if ne "dev" .App.Version }}<a class="visited-indicator" title="Release notes" href="https://github.com/glanceapp/glance/releases/tag/{{ .App.Version }}" target="_blank" rel="noreferrer">{{ .App.Version }}</a>{{ else }}({{ .App.Version }}){{ end }}
</div>
{{ else }}
{{ .App.Config.Branding.CustomFooter }}

View file

@ -1,42 +0,0 @@
package glance
import (
"strconv"
"time"
)
// UnixTime defines a timestamp encoded as epoch seconds in JSON
type UnixTime time.Time
// MarshalJSON is used to convert the timestamp to JSON
func (t UnixTime) MarshalJSON() ([]byte, error) {
return []byte(strconv.FormatInt(time.Time(t).Unix(), 10)), nil
}
// UnmarshalJSON is used to convert the timestamp from JSON
func (t *UnixTime) UnmarshalJSON(s []byte) (err error) {
r := string(s)
q, err := strconv.ParseInt(r, 10, 64)
if err != nil {
return err
}
*(*time.Time)(t) = time.Unix(q, 0)
return nil
}
// Unix returns t as a Unix time, the number of seconds elapsed
// since January 1, 1970 UTC. The result does not depend on the
// location associated with t.
func (t UnixTime) Unix() int64 {
return time.Time(t).Unix()
}
// Time returns the JSON time as a time.Time instance in UTC
func (t UnixTime) Time() time.Time {
return time.Time(t).UTC()
}
// String returns t as a formatted string
func (t UnixTime) String() string {
return t.Time().String()
}

View file

@ -157,7 +157,7 @@ type widgetBase struct {
cacheType cacheType `yaml:"-"`
nextUpdate time.Time `yaml:"-"`
updateRetriedTimes int `yaml:"-"`
HideHeader bool `yaml:"hide-header"`
HideHeader bool `yaml:"-"`
}
type widgetProviders struct {