implement oauth device code 'as if' endpoints
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# Utiliser une image officielle de Go pour construire l'application
|
# Utiliser une image officielle de Go pour construire l'application
|
||||||
FROM golang:1.20-alpine AS builder
|
FROM golang:1.23-alpine AS builder
|
||||||
|
|
||||||
# Installer les dépendances pour PostgreSQL
|
# Installer les dépendances pour PostgreSQL
|
||||||
RUN apk add --no-cache git
|
RUN apk add --no-cache git
|
||||||
|
|||||||
@@ -4,5 +4,7 @@ metadata:
|
|||||||
name: {{ include "webapp.name" . }}-config
|
name: {{ include "webapp.name" . }}-config
|
||||||
namespace: {{ .Release.Namespace }}
|
namespace: {{ .Release.Namespace }}
|
||||||
data:
|
data:
|
||||||
|
OAUTH_ALLOWED_HTTP2_AUTHORITY: webapp.arcodange.duckdns.org
|
||||||
|
OAUTH_DEVICE_CODE_ALLOWED_IPS: 90.16.102.250,
|
||||||
DATABASE_URL: postgres://pgbouncer_auth:pgbouncer_auth@pgbouncer.tools/postgres?sslmode=disable
|
DATABASE_URL: postgres://pgbouncer_auth:pgbouncer_auth@pgbouncer.tools/postgres?sslmode=disable
|
||||||
# DATABASE_URL: postgres://username:password@localhost/dbname?sslmode=disable
|
# DATABASE_URL: postgres://username:password@localhost/dbname?sslmode=disable
|
||||||
@@ -104,7 +104,8 @@ volumeMounts: []
|
|||||||
# mountPath: "/etc/foo"
|
# mountPath: "/etc/foo"
|
||||||
# readOnly: true
|
# readOnly: true
|
||||||
|
|
||||||
nodeSelector: {}
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: pi1 # entrypoint of the network, allows to avoid NAT and keep user IP
|
||||||
|
|
||||||
tolerations: []
|
tolerations: []
|
||||||
|
|
||||||
|
|||||||
7
go.mod
7
go.mod
@@ -1,5 +1,8 @@
|
|||||||
module gitea.arcodange.duckdns.org/arcodange-org/webapp
|
module gitea.arcodange.duckdns.org/arcodange-org/webapp
|
||||||
|
|
||||||
go 1.20
|
go 1.23
|
||||||
|
|
||||||
require github.com/lib/pq v1.10.9 // indirect
|
require (
|
||||||
|
github.com/lib/pq v1.10.9 // indirect
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||||
|
)
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -1,2 +1,4 @@
|
|||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
|
|||||||
81
main.go
81
main.go
@@ -5,15 +5,25 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
|
||||||
_ "github.com/lib/pq" // PostgreSQL driver
|
_ "github.com/lib/pq" // PostgreSQL driver
|
||||||
)
|
)
|
||||||
|
|
||||||
var db *sql.DB // Global database connection
|
var (
|
||||||
|
db *sql.DB // Global database connection
|
||||||
|
c = cache.New(5*time.Minute, 10*time.Minute)
|
||||||
|
oauthAllowedHttp2Authority = os.Getenv("OAUTH_ALLOWED_HTTP2_AUTHORITY") // URL authorized for device code
|
||||||
|
oauthDeviceCodeAllowedIPs = strings.Split(os.Getenv("OAUTH_DEVICE_CODE_ALLOWED_IPS"), ",") // IPS autorisées pour /retrieve
|
||||||
|
)
|
||||||
|
|
||||||
// dbConnection initializes the database connection.
|
// dbConnection initializes the database connection.
|
||||||
func dbConnection() (*sql.DB, error) {
|
func dbConnection() (*sql.DB, error) {
|
||||||
@@ -137,11 +147,26 @@ type CallbackData struct {
|
|||||||
|
|
||||||
// oauth2_callback handles HTTP requests and display a message according to queryParams
|
// oauth2_callback handles HTTP requests and display a message according to queryParams
|
||||||
func oauth2_callback(w http.ResponseWriter, r *http.Request) {
|
func oauth2_callback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
// Vérifier le référent (ou origine)
|
||||||
|
|
||||||
|
authorityHeader := r.Header.Get(":authority")
|
||||||
|
if oauthAllowedHttp2Authority != "" && authorityHeader != oauthAllowedHttp2Authority {
|
||||||
|
fmt.Println(":authority: "+authorityHeader)
|
||||||
|
http.Error(w, "Access denied: invalid referer or origin", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Récupération des paramètres query
|
// Récupération des paramètres query
|
||||||
queryParams := r.URL.Query()
|
queryParams := r.URL.Query()
|
||||||
code := queryParams.Get("code")
|
code := queryParams.Get("code")
|
||||||
state := queryParams.Get("state")
|
state := queryParams.Get("state")
|
||||||
|
|
||||||
|
// Stocker state et code dans le cache avec TTL de 5 minutes
|
||||||
|
if state != "" && code != "" {
|
||||||
|
c.Set(state, code, cache.DefaultExpiration)
|
||||||
|
}
|
||||||
|
|
||||||
// Construction d'une map pour les autres paramètres
|
// Construction d'une map pour les autres paramètres
|
||||||
otherParams := make(map[string]string)
|
otherParams := make(map[string]string)
|
||||||
for key, values := range queryParams {
|
for key, values := range queryParams {
|
||||||
@@ -250,6 +275,46 @@ func oauth2_callback(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handler pour récupérer un code à partir d'un state avec une restriction d'IP
|
||||||
|
func retrieveHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Récupérer l'IP de l'utilisateur
|
||||||
|
userIP, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||||
|
userIPforwarded := r.Header.Get("X-Forwarded-For")
|
||||||
|
if err != nil ||
|
||||||
|
!slices.Contains(oauthDeviceCodeAllowedIPs, userIP) &&
|
||||||
|
!slices.Contains(oauthDeviceCodeAllowedIPs, userIPforwarded) {
|
||||||
|
fmt.Fprintln(os.Stderr, "denied userIP: "+userIP)
|
||||||
|
// Parcourir tous les headers
|
||||||
|
for name, values := range r.Header {
|
||||||
|
// name représente le nom de l'en-tête
|
||||||
|
// values est une slice contenant toutes les valeurs associées à cet en-tête
|
||||||
|
for _, value := range values {
|
||||||
|
fmt.Fprintf(os.Stderr,"%s: %s\n", name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.Error(w, "Access denied: invalid IP", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer le paramètre `state` depuis l'URL
|
||||||
|
state := r.URL.Query().Get("state")
|
||||||
|
if state == "" {
|
||||||
|
http.Error(w, "State parameter is required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier si le state existe dans le cache
|
||||||
|
code, found := c.Get(state)
|
||||||
|
if !found {
|
||||||
|
http.Error(w, "State not found or expired", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retourner le code associé au state
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, code)
|
||||||
|
}
|
||||||
|
|
||||||
func test_oauth2_callback(w http.ResponseWriter, r *http.Request) {
|
func test_oauth2_callback(w http.ResponseWriter, r *http.Request) {
|
||||||
html := `
|
html := `
|
||||||
<html>
|
<html>
|
||||||
@@ -325,12 +390,26 @@ func main() {
|
|||||||
// Define the handler for the `/readiness` probe
|
// Define the handler for the `/readiness` probe
|
||||||
http.HandleFunc("/readiness", readinessHandler)
|
http.HandleFunc("/readiness", readinessHandler)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gitea doesn't come with device flow # https://github.com/go-gitea/gitea/issues/27309
|
||||||
|
https://gitea.arcodange.duckdns.org/.well-known/openid-configuration
|
||||||
|
"grant_types_supported": [
|
||||||
|
"authorization_code",
|
||||||
|
"refresh_token"
|
||||||
|
]
|
||||||
|
|
||||||
|
So we can use the authorization_code and redirect to this endpoint
|
||||||
|
and then the client can poll for the code matching the state it chose
|
||||||
|
*/
|
||||||
http.HandleFunc("/oauth-callback", oauth2_callback)
|
http.HandleFunc("/oauth-callback", oauth2_callback)
|
||||||
|
// Define the handler to exchange a state for a code
|
||||||
|
http.HandleFunc("/retrieve", retrieveHandler)
|
||||||
http.HandleFunc("/test-oauth-callback", test_oauth2_callback)
|
http.HandleFunc("/test-oauth-callback", test_oauth2_callback)
|
||||||
|
|
||||||
// Start the HTTP server
|
// Start the HTTP server
|
||||||
port := ":8080"
|
port := ":8080"
|
||||||
log.Printf("Server starting on port %s\n", port)
|
log.Printf("Server starting on port %s\n", port)
|
||||||
|
fmt.Println("new version indeed")
|
||||||
err = http.ListenAndServe(port, nil)
|
err = http.ListenAndServe(port, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Server failed to start: %v", err)
|
log.Fatalf("Server failed to start: %v", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user