Compare commits

...
Sign in to create a new pull request.

5 commits
main ... oidc

Author SHA1 Message Date
342d1a9ea8
build with version tag 2025-04-18 11:41:18 +02:00
135697854d
Merge branch 'main' into oidc
* main: (27 commits)
  Fix bug when making single request in job
  Add newline
  Add mountpoint:info CLI command
  Maybe fix for missing mountpoint info
  Add restart: unless-stopped
  Document function
  added unique to filter arrays with unique items in custom-api
  Only set title if attribute isn't set
  Add rounded class
  Add replaceMatches
  feat: custom-api's replaceAll with regex support
  Add filter on already seen links for RSS feeds
  Fix sorting bug in twitch channels widget
  Avoid spinning up unnecessary goroutines for single data jobs
  Print warnings when failing to get sensors
  Increase scale of everything on mobile
  Add version & sensors:print cli commands
  Fix diagnose command
  Bump go version
  Update dependencies
  ...

Signed-off-by: ThetaDev <thetadev@magenta.de>
2025-04-18 10:38:29 +02:00
9031e2dd8e
fix https config, add hide-header option 2025-03-31 03:26:08 +02:00
85f8408e9b
add slightly hacky support for different domains 2025-03-31 02:05:46 +02:00
6286c56517
add OIDC authentication 2025-03-30 03:23:02 +02:00
8 changed files with 388 additions and 21 deletions

View file

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

5
go.mod
View file

@ -3,10 +3,13 @@ module github.com/glanceapp/glance
go 1.24.2 go 1.24.2
require ( require (
github.com/coreos/go-oidc/v3 v3.13.0
github.com/fsnotify/fsnotify v1.9.0 github.com/fsnotify/fsnotify v1.9.0
github.com/google/uuid v1.6.0
github.com/mmcdole/gofeed v1.3.0 github.com/mmcdole/gofeed v1.3.0
github.com/shirou/gopsutil/v4 v4.25.3 github.com/shirou/gopsutil/v4 v4.25.3
github.com/tidwall/gjson v1.18.0 github.com/tidwall/gjson v1.18.0
golang.org/x/oauth2 v0.28.0
golang.org/x/text v0.24.0 golang.org/x/text v0.24.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@ -15,6 +18,7 @@ require (
github.com/PuerkitoBio/goquery v1.10.2 // indirect github.com/PuerkitoBio/goquery v1.10.2 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/ebitengine/purego v0.8.2 // 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/go-ole/go-ole v1.3.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
@ -27,6 +31,7 @@ require (
github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect github.com/tklauser/numcpus v0.10.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // 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/net v0.39.0 // indirect
golang.org/x/sys v0.32.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 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU= 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 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 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 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 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.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 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 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.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 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/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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= 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= github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4=
@ -39,8 +39,6 @@ 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/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 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 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 h1:SeA68lsu8gLggyMbmCn8cmp97V1TI9ld9sVzAUcKcKE=
github.com/shirou/gopsutil/v4 v4.25.3/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= 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= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -54,12 +52,8 @@ 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.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 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 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= 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 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= 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= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@ -71,6 +65,8 @@ 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.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.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@ -85,10 +81,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.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 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.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 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 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-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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -110,8 +106,6 @@ 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.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.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.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 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
@ -132,8 +126,6 @@ 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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.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.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 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 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= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

247
internal/glance/auth.go Normal file
View file

@ -0,0 +1,247 @@
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,6 +23,7 @@ type config struct {
Port uint16 `yaml:"port"` Port uint16 `yaml:"port"`
AssetsPath string `yaml:"assets-path"` AssetsPath string `yaml:"assets-path"`
BaseURL string `yaml:"base-url"` BaseURL string `yaml:"base-url"`
UsesHttps bool `yaml:"uses-https"`
StartedAt time.Time `yaml:"-"` // used in custom css file StartedAt time.Time `yaml:"-"` // used in custom css file
} `yaml:"server"` } `yaml:"server"`
@ -49,6 +50,14 @@ type config struct {
FaviconURL string `yaml:"favicon-url"` FaviconURL string `yaml:"favicon-url"`
} `yaml:"branding"` } `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"` Pages []page `yaml:"pages"`
} }
@ -96,6 +105,10 @@ func newConfigFromYAML(contents []byte) (*config, error) {
} }
} }
if config.Authentication.CookieName == "" {
config.Authentication.CookieName = "glanceauth"
}
return config, nil return config, nil
} }

View file

@ -6,12 +6,15 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"log" "log"
"log/slog"
"net/http" "net/http"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/google/uuid"
) )
var ( var (
@ -24,15 +27,26 @@ type application struct {
Version string Version string
Config config Config config
ParsedThemeStyle template.HTML ParsedThemeStyle template.HTML
Auth *GlanceAuth
slugToPage map[string]*page slugToPage map[string]*page
widgetByID map[uint64]widget widgetByID map[uint64]widget
} }
func newApplication(config *config) (*application, error) { 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{ app := &application{
Version: buildVersion, Version: buildVersion,
Config: *config, Config: *config,
Auth: auth,
slugToPage: make(map[string]*page), slugToPage: make(map[string]*page),
widgetByID: make(map[uint64]widget), widgetByID: make(map[uint64]widget),
} }
@ -130,6 +144,10 @@ type pageTemplateData struct {
} }
func (a *application) handlePageRequest(w http.ResponseWriter, r *http.Request) { func (a *application) handlePageRequest(w http.ResponseWriter, r *http.Request) {
if a.handleAuth(w, r, true) {
return
}
page, exists := a.slugToPage[r.PathValue("page")] page, exists := a.slugToPage[r.PathValue("page")]
if !exists { if !exists {
@ -154,6 +172,10 @@ func (a *application) handlePageRequest(w http.ResponseWriter, r *http.Request)
} }
func (a *application) handlePageContentRequest(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")] page, exists := a.slugToPage[r.PathValue("page")]
if !exists { if !exists {
@ -192,6 +214,10 @@ func (a *application) handleNotFound(w http.ResponseWriter, _ *http.Request) {
} }
func (a *application) handleWidgetRequest(w http.ResponseWriter, r *http.Request) { func (a *application) handleWidgetRequest(w http.ResponseWriter, r *http.Request) {
if a.handleAuth(w, r, false) {
return
}
widgetValue := r.PathValue("widget") widgetValue := r.PathValue("widget")
widgetID, err := strconv.ParseUint(widgetValue, 10, 64) widgetID, err := strconv.ParseUint(widgetValue, 10, 64)
@ -210,6 +236,43 @@ func (a *application) handleWidgetRequest(w http.ResponseWriter, r *http.Request
widget.handleRequest(w, r) 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 { func (a *application) AssetPath(asset string) string {
return a.Config.Server.BaseURL + "/static/" + staticFSHash + "/" + asset return a.Config.Server.BaseURL + "/static/" + staticFSHash + "/" + asset
} }
@ -228,6 +291,10 @@ func (a *application) server() (func() error, func() error) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
}) })
if a.Auth != nil {
mux.HandleFunc("GET /auth/resp", a.handleOidcCallback)
}
mux.Handle( mux.Handle(
fmt.Sprintf("GET /static/%s/{path...}", staticFSHash), fmt.Sprintf("GET /static/%s/{path...}", staticFSHash),
http.StripPrefix("/static/"+staticFSHash, fileServerWithCache(http.FS(staticFS), 24*time.Hour)), http.StripPrefix("/static/"+staticFSHash, fileServerWithCache(http.FS(staticFS), 24*time.Hour)),

View file

@ -0,0 +1,42 @@
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:"-"` cacheType cacheType `yaml:"-"`
nextUpdate time.Time `yaml:"-"` nextUpdate time.Time `yaml:"-"`
updateRetriedTimes int `yaml:"-"` updateRetriedTimes int `yaml:"-"`
HideHeader bool `yaml:"-"` HideHeader bool `yaml:"hide-header"`
} }
type widgetProviders struct { type widgetProviders struct {