Gate searxng, notes, and hasura behind admin auth via nginx auth_request
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled
Add ValidateAdmin endpoint that checks JWT admin claim for use as an nginx auth_request subrequest. Widen cookie path from backend endpoint to "/" so the access_token is sent on all paths. Extend access token lifetime from 24h to 7 days. Disable hasura service by default via Docker profile. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -44,7 +44,7 @@ func (r *mutationResolver) Login(ctx context.Context, input model.LoginInput) (*
|
|||||||
"access_token",
|
"access_token",
|
||||||
tokens.AccessToken,
|
tokens.AccessToken,
|
||||||
int(r.Store.Auth.Config.AccessTokenLifetime.Seconds()),
|
int(r.Store.Auth.Config.AccessTokenLifetime.Seconds()),
|
||||||
r.Store.Auth.Config.Endpoint,
|
"/",
|
||||||
r.Store.Auth.Config.Domain,
|
r.Store.Auth.Config.Domain,
|
||||||
true, true,
|
true, true,
|
||||||
)
|
)
|
||||||
@@ -52,7 +52,7 @@ func (r *mutationResolver) Login(ctx context.Context, input model.LoginInput) (*
|
|||||||
"refresh_token",
|
"refresh_token",
|
||||||
tokens.RefreshToken,
|
tokens.RefreshToken,
|
||||||
int(r.Store.Auth.Config.RefreshTokenLifetime.Seconds()),
|
int(r.Store.Auth.Config.RefreshTokenLifetime.Seconds()),
|
||||||
r.Store.Auth.Config.Endpoint,
|
"/",
|
||||||
r.Store.Auth.Config.Domain,
|
r.Store.Auth.Config.Domain,
|
||||||
true, true,
|
true, true,
|
||||||
)
|
)
|
||||||
@@ -112,7 +112,7 @@ func (r *mutationResolver) RefreshToken(ctx context.Context) (*model.AuthPayload
|
|||||||
"access_token",
|
"access_token",
|
||||||
tokens.AccessToken,
|
tokens.AccessToken,
|
||||||
int(r.Store.Auth.Config.AccessTokenLifetime.Seconds()),
|
int(r.Store.Auth.Config.AccessTokenLifetime.Seconds()),
|
||||||
r.Store.Auth.Config.Endpoint,
|
"/",
|
||||||
r.Store.Auth.Config.Domain,
|
r.Store.Auth.Config.Domain,
|
||||||
true, true,
|
true, true,
|
||||||
)
|
)
|
||||||
@@ -120,7 +120,7 @@ func (r *mutationResolver) RefreshToken(ctx context.Context) (*model.AuthPayload
|
|||||||
"refresh_token",
|
"refresh_token",
|
||||||
tokens.RefreshToken,
|
tokens.RefreshToken,
|
||||||
int(r.Store.Auth.Config.RefreshTokenLifetime.Seconds()),
|
int(r.Store.Auth.Config.RefreshTokenLifetime.Seconds()),
|
||||||
r.Store.Auth.Config.Endpoint,
|
"/",
|
||||||
r.Store.Auth.Config.Domain,
|
r.Store.Auth.Config.Domain,
|
||||||
true, true,
|
true, true,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -50,6 +50,28 @@ func (store *Store) AdminMiddleware(ctx *gin.Context) {
|
|||||||
ctx.Next()
|
ctx.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (store *Store) ValidateAdmin(ctx *gin.Context) {
|
||||||
|
accessToken, err := ctx.Cookie("access_token")
|
||||||
|
if err != nil {
|
||||||
|
ctx.Status(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := store.Auth.VerifyJWT(accessToken)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Status(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
admin, ok := (*claims)["admin"].(bool)
|
||||||
|
if !ok || !admin {
|
||||||
|
ctx.Status(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
func (store *Store) CheckToken(ctx *gin.Context) {
|
func (store *Store) CheckToken(ctx *gin.Context) {
|
||||||
access_token, err := ctx.Cookie("access_token")
|
access_token, err := ctx.Cookie("access_token")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -123,7 +145,7 @@ func (store *Store) RefreshToken(ctx *gin.Context) {
|
|||||||
"access_token",
|
"access_token",
|
||||||
tokens.AccessToken,
|
tokens.AccessToken,
|
||||||
int(store.Auth.Config.AccessTokenLifetime.Seconds()),
|
int(store.Auth.Config.AccessTokenLifetime.Seconds()),
|
||||||
store.Auth.Config.Endpoint,
|
"/",
|
||||||
store.Auth.Config.Domain,
|
store.Auth.Config.Domain,
|
||||||
true, true,
|
true, true,
|
||||||
)
|
)
|
||||||
@@ -131,7 +153,7 @@ func (store *Store) RefreshToken(ctx *gin.Context) {
|
|||||||
"refresh_token",
|
"refresh_token",
|
||||||
tokens.RefreshToken,
|
tokens.RefreshToken,
|
||||||
int(store.Auth.Config.RefreshTokenLifetime.Seconds()),
|
int(store.Auth.Config.RefreshTokenLifetime.Seconds()),
|
||||||
store.Auth.Config.Endpoint,
|
"/",
|
||||||
store.Auth.Config.Domain,
|
store.Auth.Config.Domain,
|
||||||
true, true,
|
true, true,
|
||||||
)
|
)
|
||||||
@@ -169,7 +191,7 @@ func (store *Store) Login(ctx *gin.Context) {
|
|||||||
"access_token",
|
"access_token",
|
||||||
tokens.AccessToken,
|
tokens.AccessToken,
|
||||||
int(store.Auth.Config.AccessTokenLifetime.Seconds()),
|
int(store.Auth.Config.AccessTokenLifetime.Seconds()),
|
||||||
store.Auth.Config.Endpoint,
|
"/",
|
||||||
store.Auth.Config.Domain,
|
store.Auth.Config.Domain,
|
||||||
true, true,
|
true, true,
|
||||||
)
|
)
|
||||||
@@ -177,7 +199,7 @@ func (store *Store) Login(ctx *gin.Context) {
|
|||||||
"refresh_token",
|
"refresh_token",
|
||||||
tokens.RefreshToken,
|
tokens.RefreshToken,
|
||||||
int(store.Auth.Config.RefreshTokenLifetime.Seconds()),
|
int(store.Auth.Config.RefreshTokenLifetime.Seconds()),
|
||||||
store.Auth.Config.Endpoint,
|
"/",
|
||||||
store.Auth.Config.Domain,
|
store.Auth.Config.Domain,
|
||||||
true, true,
|
true, true,
|
||||||
)
|
)
|
||||||
@@ -197,7 +219,7 @@ func (store *Store) removeCookies(ctx *gin.Context) {
|
|||||||
"access_token",
|
"access_token",
|
||||||
"",
|
"",
|
||||||
-1,
|
-1,
|
||||||
store.Auth.Config.Endpoint,
|
"/",
|
||||||
store.Auth.Config.Domain,
|
store.Auth.Config.Domain,
|
||||||
true, true,
|
true, true,
|
||||||
)
|
)
|
||||||
@@ -205,7 +227,7 @@ func (store *Store) removeCookies(ctx *gin.Context) {
|
|||||||
"refresh_token",
|
"refresh_token",
|
||||||
"",
|
"",
|
||||||
-1,
|
-1,
|
||||||
store.Auth.Config.Endpoint,
|
"/",
|
||||||
store.Auth.Config.Domain,
|
store.Auth.Config.Domain,
|
||||||
true, true,
|
true, true,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ func (store *Store) DeleteUser(ctx *gin.Context) {
|
|||||||
"access_token",
|
"access_token",
|
||||||
"",
|
"",
|
||||||
-1,
|
-1,
|
||||||
store.Auth.Config.Endpoint,
|
"/",
|
||||||
store.Auth.Config.Domain,
|
store.Auth.Config.Domain,
|
||||||
true, true,
|
true, true,
|
||||||
)
|
)
|
||||||
@@ -181,7 +181,7 @@ func (store *Store) DeleteUser(ctx *gin.Context) {
|
|||||||
"refresh_token",
|
"refresh_token",
|
||||||
"",
|
"",
|
||||||
-1,
|
-1,
|
||||||
store.Auth.Config.Endpoint,
|
"/",
|
||||||
store.Auth.Config.Domain,
|
store.Auth.Config.Domain,
|
||||||
true, true,
|
true, true,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func main() {
|
|||||||
|
|
||||||
authSecret := os.Getenv("BACKEND_SECRET")
|
authSecret := os.Getenv("BACKEND_SECRET")
|
||||||
backendEndpoint := os.Getenv("BACKEND_ENDPOINT")
|
backendEndpoint := os.Getenv("BACKEND_ENDPOINT")
|
||||||
accessTokenLifetime := 24 * time.Hour
|
accessTokenLifetime := 7 * 24 * time.Hour
|
||||||
refreshTokenLifetime := 365 * 24 * time.Hour
|
refreshTokenLifetime := 365 * 24 * time.Hour
|
||||||
authConfig := services.AuthConfig{Secret: []byte(authSecret), Domain: domainName, RefreshTokenLifetime: refreshTokenLifetime, AccessTokenLifetime: accessTokenLifetime, Endpoint: backendEndpoint}
|
authConfig := services.AuthConfig{Secret: []byte(authSecret), Domain: domainName, RefreshTokenLifetime: refreshTokenLifetime, AccessTokenLifetime: accessTokenLifetime, Endpoint: backendEndpoint}
|
||||||
auth := services.InitAuth(&authConfig)
|
auth := services.InitAuth(&authConfig)
|
||||||
@@ -122,6 +122,7 @@ func main() {
|
|||||||
r.POST("/auth/refresh", store.RefreshToken)
|
r.POST("/auth/refresh", store.RefreshToken)
|
||||||
r.GET("/auth/check", store.CheckToken)
|
r.GET("/auth/check", store.CheckToken)
|
||||||
r.POST("/auth/logout", store.Logout)
|
r.POST("/auth/logout", store.Logout)
|
||||||
|
r.GET("/auth/validate-admin", store.ValidateAdmin)
|
||||||
|
|
||||||
// SPOTIFY
|
// SPOTIFY
|
||||||
r.GET("/spotify/callback", store.CompleteSpotifyAuth)
|
r.GET("/spotify/callback", store.CompleteSpotifyAuth)
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ services:
|
|||||||
- backend
|
- backend
|
||||||
- icecast2
|
- icecast2
|
||||||
- gitea
|
- gitea
|
||||||
- hasura
|
|
||||||
- quartz
|
- quartz
|
||||||
- searxng
|
- searxng
|
||||||
networks:
|
networks:
|
||||||
@@ -96,6 +95,8 @@ services:
|
|||||||
image: hasura/graphql-engine:v2.44.0
|
image: hasura/graphql-engine:v2.44.0
|
||||||
container_name: "${HASURA_HOST}"
|
container_name: "${HASURA_HOST}"
|
||||||
restart: always
|
restart: always
|
||||||
|
profiles:
|
||||||
|
- disabled
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
networks:
|
networks:
|
||||||
@@ -135,7 +136,6 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ${OBSIDIAN_DIR}:/quartz/content:ro
|
- ${OBSIDIAN_DIR}:/quartz/content:ro
|
||||||
|
|
||||||
|
|
||||||
searxng:
|
searxng:
|
||||||
build:
|
build:
|
||||||
context: ./searxng
|
context: ./searxng
|
||||||
@@ -151,7 +151,6 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- searxng_data:/etc/searxng
|
- searxng_data:/etc/searxng
|
||||||
|
|
||||||
|
|
||||||
gitea:
|
gitea:
|
||||||
image: docker.gitea.com/gitea:1.25.4-rootless
|
image: docker.gitea.com/gitea:1.25.4-rootless
|
||||||
container_name: "${GITEA_HOST}"
|
container_name: "${GITEA_HOST}"
|
||||||
|
|||||||
@@ -207,6 +207,8 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /hasura/ {
|
location /hasura/ {
|
||||||
|
auth_request /internal/auth/admin-validate;
|
||||||
|
error_page 401 403 = @auth_denied;
|
||||||
proxy_pass http://$HASURA_HOST:$HASURA_PORT/;
|
proxy_pass http://$HASURA_HOST:$HASURA_PORT/;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
@@ -222,6 +224,8 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /notes/ {
|
location /notes/ {
|
||||||
|
auth_request /internal/auth/admin-validate;
|
||||||
|
error_page 401 403 = @auth_denied;
|
||||||
proxy_pass http://$QUARTZ_HOST:$QUARTZ_PORT/;
|
proxy_pass http://$QUARTZ_HOST:$QUARTZ_PORT/;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
@@ -233,11 +237,25 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
location = /internal/auth/admin-validate {
|
||||||
|
internal;
|
||||||
|
proxy_pass http://$BACKEND_HOST:$BACKEND_PORT/auth/validate-admin;
|
||||||
|
proxy_pass_request_body off;
|
||||||
|
proxy_set_header Content-Length "";
|
||||||
|
proxy_set_header Cookie $http_cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
location @auth_denied {
|
||||||
|
return 302 /;
|
||||||
|
}
|
||||||
|
|
||||||
location /searxng {
|
location /searxng {
|
||||||
return 301 /searxng/;
|
return 301 /searxng/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /searxng/ {
|
location /searxng/ {
|
||||||
|
auth_request /internal/auth/admin-validate;
|
||||||
|
error_page 401 403 = @auth_denied;
|
||||||
proxy_pass http://$SEARXNG_HOST:$SEARXNG_PORT/;
|
proxy_pass http://$SEARXNG_HOST:$SEARXNG_PORT/;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
|||||||
@@ -134,6 +134,8 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /hasura/ {
|
location /hasura/ {
|
||||||
|
auth_request /internal/auth/admin-validate;
|
||||||
|
error_page 401 403 = @auth_denied;
|
||||||
proxy_pass http://$HASURA_HOST:$HASURA_PORT/;
|
proxy_pass http://$HASURA_HOST:$HASURA_PORT/;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
@@ -149,6 +151,8 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /notes/ {
|
location /notes/ {
|
||||||
|
auth_request /internal/auth/admin-validate;
|
||||||
|
error_page 401 403 = @auth_denied;
|
||||||
proxy_pass http://$QUARTZ_HOST:$QUARTZ_PORT/;
|
proxy_pass http://$QUARTZ_HOST:$QUARTZ_PORT/;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
@@ -160,11 +164,25 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
location = /internal/auth/admin-validate {
|
||||||
|
internal;
|
||||||
|
proxy_pass http://$BACKEND_HOST:$BACKEND_PORT/auth/validate-admin;
|
||||||
|
proxy_pass_request_body off;
|
||||||
|
proxy_set_header Content-Length "";
|
||||||
|
proxy_set_header Cookie $http_cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
location @auth_denied {
|
||||||
|
return 302 /;
|
||||||
|
}
|
||||||
|
|
||||||
location /searxng {
|
location /searxng {
|
||||||
return 301 /searxng/;
|
return 301 /searxng/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /searxng/ {
|
location /searxng/ {
|
||||||
|
auth_request /internal/auth/admin-validate;
|
||||||
|
error_page 401 403 = @auth_denied;
|
||||||
proxy_pass http://$SEARXNG_HOST:$SEARXNG_PORT/;
|
proxy_pass http://$SEARXNG_HOST:$SEARXNG_PORT/;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
@@ -272,6 +290,8 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /hasura/ {
|
location /hasura/ {
|
||||||
|
auth_request /internal/auth/admin-validate;
|
||||||
|
error_page 401 403 = @auth_denied;
|
||||||
proxy_pass http://$HASURA_HOST:$HASURA_PORT/;
|
proxy_pass http://$HASURA_HOST:$HASURA_PORT/;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
@@ -287,6 +307,8 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /notes/ {
|
location /notes/ {
|
||||||
|
auth_request /internal/auth/admin-validate;
|
||||||
|
error_page 401 403 = @auth_denied;
|
||||||
proxy_pass http://$QUARTZ_HOST:$QUARTZ_PORT/;
|
proxy_pass http://$QUARTZ_HOST:$QUARTZ_PORT/;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
@@ -298,11 +320,25 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
location = /internal/auth/admin-validate {
|
||||||
|
internal;
|
||||||
|
proxy_pass http://$BACKEND_HOST:$BACKEND_PORT/auth/validate-admin;
|
||||||
|
proxy_pass_request_body off;
|
||||||
|
proxy_set_header Content-Length "";
|
||||||
|
proxy_set_header Cookie $http_cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
location @auth_denied {
|
||||||
|
return 302 /;
|
||||||
|
}
|
||||||
|
|
||||||
location /searxng {
|
location /searxng {
|
||||||
return 301 /searxng/;
|
return 301 /searxng/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /searxng/ {
|
location /searxng/ {
|
||||||
|
auth_request /internal/auth/admin-validate;
|
||||||
|
error_page 401 403 = @auth_denied;
|
||||||
proxy_pass http://$SEARXNG_HOST:$SEARXNG_PORT/;
|
proxy_pass http://$SEARXNG_HOST:$SEARXNG_PORT/;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
|||||||
Reference in New Issue
Block a user