Compare commits
No commits in common. "oidc" and "main" have entirely different histories.
8 changed files with 21 additions and 388 deletions
|
@ -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
5
go.mod
|
@ -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
30
go.sum
|
@ -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=
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue