mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-06-12 14:30:18 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b75fe9ac1e | |||
| 5c5d7a43ef | |||
| 6a4d85dc41 | |||
| 3c9817cf39 | |||
| ede6e8084d | |||
| 4e671ed48c | |||
| a69d22bb0e | |||
| ace64fa7ee | |||
| 5e954da5ff | |||
| 47b7f1e6f2 | |||
| f078e3549e | |||
| da9079246a | |||
| 2454ba58ea | |||
| 97e0e0dfff | |||
| b3c152fa1c | |||
| 5caee887de | |||
| b5770ef305 | |||
| 1c4ca8f436 | |||
| a72300484b | |||
| 4fe5de241b | |||
| 83ed9ece57 | |||
| faa3156672 | |||
| 695feca71c | |||
| 82d21c3b28 | |||
| fe8463890a | |||
| ac9689dc9b | |||
| 3e5757cfc9 | |||
| ed94490efd |
@@ -206,8 +206,6 @@ TINYAUTH_LDAP_ADDRESS=
|
||||
TINYAUTH_LDAP_BINDDN=
|
||||
# Bind password for LDAP authentication.
|
||||
TINYAUTH_LDAP_BINDPASSWORD=
|
||||
# Path to the Bind password.
|
||||
TINYAUTH_LDAP_BINDPASSWORDFILE=
|
||||
# Base DN for LDAP searches.
|
||||
TINYAUTH_LDAP_BASEDN=
|
||||
# Allow insecure LDAP connections.
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||
with:
|
||||
go-version: "^1.26.4"
|
||||
go-version: "^1.26.0"
|
||||
|
||||
- name: Go dependencies
|
||||
run: go mod download
|
||||
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
- name: Install go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||
with:
|
||||
go-version: "^1.26.4"
|
||||
go-version: "^1.26.0"
|
||||
|
||||
- name: Install frontend dependencies
|
||||
working-directory: ./frontend
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
- name: Install go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||
with:
|
||||
go-version: "^1.26.4"
|
||||
go-version: "^1.26.0"
|
||||
|
||||
- name: Install frontend dependencies
|
||||
working-directory: ./frontend
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
- name: Install go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||
with:
|
||||
go-version: "^1.26.4"
|
||||
go-version: "^1.26.0"
|
||||
|
||||
- name: Install frontend dependencies
|
||||
working-directory: ./frontend
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
- name: Install go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
|
||||
with:
|
||||
go-version: "^1.26.4"
|
||||
go-version: "^1.26.0"
|
||||
|
||||
- name: Install frontend dependencies
|
||||
working-directory: ./frontend
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ Contributing to Tinyauth is straightforward. Follow the steps below to set up a
|
||||
## Requirements
|
||||
|
||||
- pnpm
|
||||
- Golang v1.26.4 or later
|
||||
- Golang v1.24.0 or later
|
||||
- Git
|
||||
- Docker
|
||||
- Make
|
||||
|
||||
@@ -15,7 +15,7 @@ export const useRedirectUri = (
|
||||
let isAllowedProto = false;
|
||||
let isHttpsDowngrade = false;
|
||||
|
||||
if (redirect_uri === undefined) {
|
||||
if (!redirect_uri) {
|
||||
return {
|
||||
valid: isValid,
|
||||
trusted: isTrusted,
|
||||
|
||||
@@ -6,7 +6,6 @@ type ScreenParams = {
|
||||
oidc_ticket?: string;
|
||||
oidc_scope?: string;
|
||||
oidc_name?: string;
|
||||
oidc_show_consent?: boolean;
|
||||
};
|
||||
|
||||
const zodScreenParams = z.object({
|
||||
@@ -15,7 +14,6 @@ const zodScreenParams = z.object({
|
||||
oidc_ticket: z.string().optional(),
|
||||
oidc_scope: z.string().optional(),
|
||||
oidc_name: z.string().optional(),
|
||||
oidc_show_consent: z.stringbool().optional(),
|
||||
});
|
||||
|
||||
export function useScreenParams(params: URLSearchParams): ScreenParams {
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
recompileScreenParams,
|
||||
useScreenParams,
|
||||
} from "@/lib/hooks/screen-params";
|
||||
import { useEffect } from "react";
|
||||
|
||||
type Scope = {
|
||||
id: string;
|
||||
@@ -91,54 +90,27 @@ export const AuthorizePage = () => {
|
||||
const isOidc = screenParams.login_for === "oidc";
|
||||
const compiledParams = recompileScreenParams(screenParams);
|
||||
|
||||
const { mutate: authorizeMutate, isPending: authorizeIsPending } =
|
||||
useMutation({
|
||||
mutationFn: () => {
|
||||
return axios.post("/api/oidc/authorize-complete", {
|
||||
ticket: screenParams.oidc_ticket,
|
||||
});
|
||||
},
|
||||
mutationKey: ["authorize", screenParams.oidc_ticket],
|
||||
onSuccess: (data) => {
|
||||
toast.info(t("authorizeSuccessTitle"), {
|
||||
description: t("authorizeSuccessSubtitle"),
|
||||
});
|
||||
window.location.replace(data.data.redirect_uri);
|
||||
},
|
||||
onError: (error) => {
|
||||
window.location.replace(
|
||||
`/error?error=${encodeURIComponent(error.message)}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
const authorizeMutation = useMutation({
|
||||
mutationFn: () => {
|
||||
return axios.post("/api/oidc/authorize-complete", {
|
||||
ticket: screenParams.oidc_ticket,
|
||||
});
|
||||
},
|
||||
mutationKey: ["authorize", screenParams.oidc_ticket],
|
||||
onSuccess: (data) => {
|
||||
toast.info(t("authorizeSuccessTitle"), {
|
||||
description: t("authorizeSuccessSubtitle"),
|
||||
});
|
||||
window.location.replace(data.data.redirect_uri);
|
||||
},
|
||||
onError: (error) => {
|
||||
window.location.replace(
|
||||
`/error?error=${encodeURIComponent(error.message)}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!isOidc ||
|
||||
screenParams.oidc_ticket === undefined ||
|
||||
screenParams.oidc_scope === undefined ||
|
||||
!auth.authenticated
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (screenParams.oidc_show_consent === false) {
|
||||
authorizeMutate();
|
||||
}
|
||||
}, [
|
||||
isOidc,
|
||||
screenParams.oidc_ticket,
|
||||
screenParams.oidc_scope,
|
||||
screenParams.oidc_show_consent,
|
||||
auth.authenticated,
|
||||
authorizeMutate,
|
||||
]);
|
||||
|
||||
if (
|
||||
!isOidc ||
|
||||
screenParams.oidc_ticket === undefined ||
|
||||
screenParams.oidc_scope === undefined
|
||||
) {
|
||||
if (!isOidc || !screenParams.oidc_ticket || !screenParams.oidc_scope) {
|
||||
return (
|
||||
<Navigate
|
||||
to={`/error?error=${encodeURIComponent(t("authorizeErrorInvalidParams"))}`}
|
||||
@@ -154,19 +126,6 @@ export const AuthorizePage = () => {
|
||||
const scopes =
|
||||
screenParams.oidc_scope.split(" ").filter((s) => s.trim() !== "") || [];
|
||||
|
||||
if (screenParams.oidc_show_consent === false) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="gap-1.5">
|
||||
<CardTitle className="text-xl">Authorizing</CardTitle>
|
||||
<CardDescription>
|
||||
You will soon be redirected to your application...
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="mb-2">
|
||||
@@ -208,12 +167,15 @@ export const AuthorizePage = () => {
|
||||
</CardContent>
|
||||
)}
|
||||
<CardFooter className="flex flex-col items-stretch gap-3">
|
||||
<Button onClick={() => authorizeMutate()} loading={authorizeIsPending}>
|
||||
<Button
|
||||
onClick={() => authorizeMutation.mutate()}
|
||||
loading={authorizeMutation.isPending}
|
||||
>
|
||||
{t("authorizeTitle")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => navigate(`/logout${compiledParams}`)}
|
||||
disabled={authorizeIsPending}
|
||||
disabled={authorizeMutation.isPending}
|
||||
variant="outline"
|
||||
>
|
||||
{t("cancelTitle")}
|
||||
|
||||
@@ -11,7 +11,7 @@ export const ErrorPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const { search } = useLocation();
|
||||
const searchParams = new URLSearchParams(search);
|
||||
const error = searchParams.get("error") ?? "";
|
||||
const error = searchParams.get("error") || "";
|
||||
|
||||
return (
|
||||
<Card>
|
||||
|
||||
@@ -168,7 +168,8 @@ export const LoginPage = () => {
|
||||
!auth.authenticated &&
|
||||
isOauthAutoRedirect &&
|
||||
!hasAutoRedirectedRef.current &&
|
||||
screenParams.login_for !== undefined
|
||||
screenParams.redirect_uri &&
|
||||
screenParams.login_for
|
||||
) {
|
||||
hasAutoRedirectedRef.current = true;
|
||||
oauthMutate(oauth.autoRedirect);
|
||||
@@ -180,6 +181,7 @@ export const LoginPage = () => {
|
||||
oauth.autoRedirect,
|
||||
isOauthAutoRedirect,
|
||||
screenParams.login_for,
|
||||
screenParams.redirect_uri,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -67,24 +67,15 @@ func run() error {
|
||||
Overlay: map[string][]byte{outPath: stub},
|
||||
}
|
||||
|
||||
repoPkgPath := parentPkg(*driverPkg)
|
||||
|
||||
pkgs, err := loadMultiplePkgs(cfg, *driverPkg, repoPkgPath)
|
||||
|
||||
driverTypePkg, err := loadOnePkg(cfg, *driverPkg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load packages: %w", err)
|
||||
return fmt.Errorf("load driver package: %w", err)
|
||||
}
|
||||
|
||||
driverTypePkg, ok := pkgs[*driverPkg]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("driver package %s not found in loaded packages", *driverPkg)
|
||||
}
|
||||
|
||||
repoTypePkg, ok := pkgs[repoPkgPath]
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("repository package %s not found in loaded packages", repoPkgPath)
|
||||
repoPkgPath := parentPkg(*driverPkg)
|
||||
repoTypePkg, err := loadOnePkg(cfg, repoPkgPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load repo package: %w", err)
|
||||
}
|
||||
|
||||
if err := validateStructShapes(driverTypePkg, repoTypePkg); err != nil {
|
||||
@@ -115,25 +106,25 @@ func run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadMultiplePkgs loads multiple packages via cfg and returns a map of import path → *types.Package,
|
||||
// or an error if any package fails to load or has type errors.
|
||||
func loadMultiplePkgs(cfg *packages.Config, importPaths ...string) (map[string]*types.Package, error) {
|
||||
pkgs, err := packages.Load(cfg, importPaths...)
|
||||
// loadOnePkg loads a single package via cfg and returns its *types.Package,
|
||||
// or an error if the package fails to load or has type errors.
|
||||
func loadOnePkg(cfg *packages.Config, importPath string) (*types.Package, error) {
|
||||
pkgs, err := packages.Load(cfg, importPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load %v: %w", importPaths, err)
|
||||
return nil, fmt.Errorf("load %s: %w", importPath, err)
|
||||
}
|
||||
out := make(map[string]*types.Package)
|
||||
for _, pkg := range pkgs {
|
||||
if len(pkg.Errors) > 0 {
|
||||
msgs := make([]string, len(pkg.Errors))
|
||||
for i, e := range pkg.Errors {
|
||||
msgs[i] = e.Error()
|
||||
}
|
||||
return nil, fmt.Errorf("package %s has errors:\n %s", pkg.PkgPath, strings.Join(msgs, "\n "))
|
||||
if len(pkgs) != 1 {
|
||||
return nil, fmt.Errorf("expected 1 package for %s, got %d", importPath, len(pkgs))
|
||||
}
|
||||
pkg := pkgs[0]
|
||||
if len(pkg.Errors) > 0 {
|
||||
msgs := make([]string, len(pkg.Errors))
|
||||
for i, e := range pkg.Errors {
|
||||
msgs[i] = e.Error()
|
||||
}
|
||||
out[pkg.PkgPath] = pkg.Types
|
||||
return nil, fmt.Errorf("package %s has errors:\n %s", importPath, strings.Join(msgs, "\n "))
|
||||
}
|
||||
return out, nil
|
||||
return pkg.Types, nil
|
||||
}
|
||||
|
||||
// parentPkg returns the parent import path (everything before the last /).
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS "oidc_consent" (
|
||||
"uuid" TEXT NOT NULL UNIQUE PRIMARY KEY,
|
||||
"client_id" TEXT NOT NULL,
|
||||
"scopes" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
@@ -1 +0,0 @@
|
||||
DROP TABLE IF EXISTS "oidc_consent";
|
||||
@@ -1 +0,0 @@
|
||||
DROP TABLE IF EXISTS "oidc_consent";
|
||||
@@ -1,7 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS "oidc_consent" (
|
||||
"uuid" TEXT NOT NULL UNIQUE PRIMARY KEY,
|
||||
"client_id" TEXT NOT NULL,
|
||||
"scopes" TEXT NOT NULL,
|
||||
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
@@ -47,7 +47,6 @@ type Services struct {
|
||||
type BootstrapApp struct {
|
||||
config model.Config
|
||||
runtime model.RuntimeConfig
|
||||
helpers model.RuntimeHelpers
|
||||
services Services
|
||||
log *logger.Logger
|
||||
ctx context.Context
|
||||
@@ -186,8 +185,9 @@ func (app *BootstrapApp) Setup() error {
|
||||
cookieId := strings.Split(app.runtime.UUID, "-")[0] // first 8 characters of the uuid should be good enough
|
||||
|
||||
app.runtime.SessionCookieName = fmt.Sprintf("%s-%s", model.SessionCookieName, cookieId)
|
||||
app.runtime.CSRFCookieName = fmt.Sprintf("%s-%s", model.CSRFCookieName, cookieId)
|
||||
app.runtime.RedirectCookieName = fmt.Sprintf("%s-%s", model.RedirectCookieName, cookieId)
|
||||
app.runtime.OAuthSessionCookieName = fmt.Sprintf("%s-%s", model.OAuthSessionCookieName, cookieId)
|
||||
app.runtime.ConsentCookieName = fmt.Sprintf("%s-%s", model.ConsentCookieName, cookieId)
|
||||
|
||||
// database
|
||||
store, err := app.SetupStore()
|
||||
@@ -264,9 +264,6 @@ func (app *BootstrapApp) Setup() error {
|
||||
app.runtime.TrustedDomains = append(app.runtime.TrustedDomains, "https://"+app.services.tailscaleService.GetHostname())
|
||||
}
|
||||
|
||||
// runtime helpers
|
||||
app.helpers.GetCookieDomain = app.getCookieDomain
|
||||
|
||||
// setup router
|
||||
err = app.setupRouter()
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
)
|
||||
|
||||
// Not really the best place for the helpers to be but it works because bootstrap app provides
|
||||
// them with everything they need
|
||||
|
||||
func (app *BootstrapApp) getCookieDomain(ctx context.Context, ip string) (string, error) {
|
||||
cookieDomain := app.runtime.CookieDomain
|
||||
|
||||
if app.isTailscaleRequest(ctx, ip) {
|
||||
if app.services.tailscaleService == nil {
|
||||
return "", errors.New("tailscale service is not configured")
|
||||
}
|
||||
|
||||
tsCookieDomain, err := utils.GetCookieDomain(fmt.Sprintf("https://%s", app.services.tailscaleService.GetHostname()))
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get cookie domain for tailscale user: %w", err)
|
||||
}
|
||||
|
||||
cookieDomain = tsCookieDomain
|
||||
}
|
||||
|
||||
if app.config.Auth.SubdomainsEnabled {
|
||||
cookieDomain = "." + cookieDomain
|
||||
}
|
||||
|
||||
return cookieDomain, nil
|
||||
}
|
||||
|
||||
func (app *BootstrapApp) isTailscaleRequest(ctx context.Context, ip string) bool {
|
||||
if app.services.tailscaleService == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
whois, err := app.services.tailscaleService.Whois(ctx, ip)
|
||||
|
||||
if err != nil {
|
||||
app.log.App.Error().Err(err).Msgf("Error performing Tailscale whois for IP %s: %v", ip, err)
|
||||
return false
|
||||
}
|
||||
|
||||
if whois == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -58,8 +58,8 @@ func (app *BootstrapApp) setupRouter() error {
|
||||
apiRouter := engine.Group("/api")
|
||||
|
||||
controller.NewContextController(app.log, app.config, app.runtime, apiRouter)
|
||||
controller.NewOAuthController(app.log, app.config, app.runtime, &app.helpers, apiRouter, app.services.authService)
|
||||
controller.NewOIDCController(app.log, app.services.oidcService, app.runtime, &app.helpers, app.config, apiRouter, &engine.RouterGroup)
|
||||
controller.NewOAuthController(app.log, app.config, app.runtime, apiRouter, app.services.authService)
|
||||
controller.NewOIDCController(app.log, app.services.oidcService, app.runtime, apiRouter, &engine.RouterGroup)
|
||||
controller.NewProxyController(app.log, app.runtime, apiRouter, app.services.accessControlService, app.services.authService, app.services.policyEngine)
|
||||
controller.NewUserController(app.log, app.runtime, apiRouter, app.services.authService)
|
||||
controller.NewResourcesController(app.config, &engine.RouterGroup)
|
||||
|
||||
@@ -42,7 +42,7 @@ func (app *BootstrapApp) setupServices() error {
|
||||
oauthBrokerService := service.NewOAuthBrokerService(app.log, app.runtime.OAuthProviders, app.ctx)
|
||||
app.services.oauthBrokerService = oauthBrokerService
|
||||
|
||||
authService := service.NewAuthService(app.log, app.config, app.runtime, &app.helpers, app.ctx, app.ding, app.services.ldapService, app.queries, app.services.oauthBrokerService, app.services.tailscaleService, app.services.policyEngine)
|
||||
authService := service.NewAuthService(app.log, app.config, app.runtime, app.ctx, app.ding, app.services.ldapService, app.queries, app.services.oauthBrokerService, app.services.tailscaleService, app.services.policyEngine)
|
||||
app.services.authService = authService
|
||||
|
||||
oidcService, err := service.NewOIDCService(app.log, app.config, app.runtime, app.queries, app.ding)
|
||||
|
||||
@@ -24,7 +24,6 @@ type OAuthController struct {
|
||||
log *logger.Logger
|
||||
config model.Config
|
||||
runtime model.RuntimeConfig
|
||||
helpers *model.RuntimeHelpers
|
||||
auth *service.AuthService
|
||||
}
|
||||
|
||||
@@ -32,7 +31,6 @@ func NewOAuthController(
|
||||
log *logger.Logger,
|
||||
config model.Config,
|
||||
runtimeConfig model.RuntimeConfig,
|
||||
helpers *model.RuntimeHelpers,
|
||||
router *gin.RouterGroup,
|
||||
auth *service.AuthService,
|
||||
) *OAuthController {
|
||||
@@ -40,7 +38,6 @@ func NewOAuthController(
|
||||
log: log,
|
||||
config: config,
|
||||
runtime: runtimeConfig,
|
||||
helpers: helpers,
|
||||
auth: auth,
|
||||
}
|
||||
|
||||
@@ -108,18 +105,7 @@ func (controller *OAuthController) oauthURLHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
cookieDomain, err := controller.helpers.GetCookieDomain(c, c.RemoteIP())
|
||||
|
||||
if err != nil {
|
||||
controller.log.App.Error().Err(err).Msg("Failed to determine cookie domain")
|
||||
c.JSON(500, gin.H{
|
||||
"status": 500,
|
||||
"message": "Internal Server Error",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.SetCookie(controller.runtime.OAuthSessionCookieName, sessionId, int(time.Hour.Seconds()), "/", cookieDomain, controller.config.Auth.SecureCookie, true)
|
||||
c.SetCookie(controller.runtime.OAuthSessionCookieName, sessionId, int(time.Hour.Seconds()), "/", controller.getCookieDomain(), controller.config.Auth.SecureCookie, true)
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": 200,
|
||||
@@ -149,15 +135,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
cookieDomain, err := controller.helpers.GetCookieDomain(c, c.RemoteIP())
|
||||
|
||||
if err != nil {
|
||||
controller.log.App.Error().Err(err).Msg("Failed to determine cookie domain")
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.runtime.AppURL))
|
||||
return
|
||||
}
|
||||
|
||||
c.SetCookie(controller.runtime.OAuthSessionCookieName, "", -1, "/", cookieDomain, controller.config.Auth.SecureCookie, true)
|
||||
c.SetCookie(controller.runtime.OAuthSessionCookieName, "", -1, "/", controller.getCookieDomain(), controller.config.Auth.SecureCookie, true)
|
||||
|
||||
oauthPendingSession, err := controller.auth.GetOAuthPendingSession(sessionIdCookie)
|
||||
|
||||
@@ -274,7 +252,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
|
||||
controller.log.App.Debug().Msg("Creating session cookie for user")
|
||||
|
||||
cookie, err := controller.auth.CreateSession(c, sessionCookie, c.RemoteIP())
|
||||
cookie, err := controller.auth.CreateSession(c, sessionCookie)
|
||||
|
||||
if err != nil {
|
||||
controller.log.App.Error().Err(err).Msg("Failed to create session cookie")
|
||||
@@ -320,3 +298,10 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
func (controller *OAuthController) isOidcRequest(params service.OAuthCallbackParams) bool {
|
||||
return params.LoginFor == string(FrontendLoginForOIDC)
|
||||
}
|
||||
|
||||
func (controller *OAuthController) getCookieDomain() string {
|
||||
if controller.config.Auth.SubdomainsEnabled {
|
||||
return "." + controller.runtime.CookieDomain
|
||||
}
|
||||
return controller.runtime.CookieDomain
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
@@ -33,8 +31,6 @@ type OIDCController struct {
|
||||
log *logger.Logger
|
||||
oidc *service.OIDCService
|
||||
runtime model.RuntimeConfig
|
||||
helpers *model.RuntimeHelpers
|
||||
config model.Config
|
||||
}
|
||||
|
||||
type AuthorizeCallback struct {
|
||||
@@ -72,11 +68,10 @@ type ClientCredentials struct {
|
||||
}
|
||||
|
||||
type AuthorizeScreenParams struct {
|
||||
LoginFor FrontendLoginFor `url:"login_for"`
|
||||
OIDCTicket string `url:"oidc_ticket"`
|
||||
OIDCScope string `url:"oidc_scope"`
|
||||
OIDCName string `url:"oidc_name"`
|
||||
OIDCShowConsent bool `url:"oidc_show_consent"`
|
||||
LoginFor FrontendLoginFor `url:"login_for"`
|
||||
OIDCTicket string `url:"oidc_ticket"`
|
||||
OIDCScope string `url:"oidc_scope"`
|
||||
OIDCName string `url:"oidc_name"`
|
||||
}
|
||||
|
||||
type AuthorizeCompleteRequest struct {
|
||||
@@ -87,16 +82,12 @@ func NewOIDCController(
|
||||
log *logger.Logger,
|
||||
oidcService *service.OIDCService,
|
||||
runtimeConfig model.RuntimeConfig,
|
||||
helpers *model.RuntimeHelpers,
|
||||
config model.Config,
|
||||
router *gin.RouterGroup,
|
||||
mainRouter *gin.RouterGroup) *OIDCController {
|
||||
controller := &OIDCController{
|
||||
log: log,
|
||||
oidc: oidcService,
|
||||
runtime: runtimeConfig,
|
||||
helpers: helpers,
|
||||
config: config,
|
||||
}
|
||||
|
||||
mainRouter.POST("/authorize", controller.authorize)
|
||||
@@ -172,31 +163,11 @@ func (controller *OIDCController) authorize(c *gin.Context) {
|
||||
|
||||
ticket := controller.oidc.CreateAuthorizeRequestTicket(*req)
|
||||
|
||||
// Check if we have consented before for this client and scope
|
||||
consnetCookie, err := c.Cookie(controller.runtime.ConsentCookieName)
|
||||
|
||||
showConsent := true
|
||||
|
||||
if err == nil {
|
||||
consentEntry, err := controller.oidc.GetConsentEntry(c, consnetCookie)
|
||||
|
||||
if err == nil && consentEntry != nil {
|
||||
if consentEntry.ClientID == req.ClientID && consentEntry.Scopes == req.Scope {
|
||||
showConsent = false
|
||||
}
|
||||
} else {
|
||||
if !errors.Is(err, sql.ErrNoRows) {
|
||||
controller.log.App.Error().Err(err).Msg("Failed to get consent entry for consent cookie")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queries, err := query.Values(AuthorizeScreenParams{
|
||||
LoginFor: FrontendLoginForOIDC,
|
||||
OIDCTicket: ticket,
|
||||
OIDCScope: req.Scope,
|
||||
OIDCName: client.Name,
|
||||
OIDCShowConsent: showConsent,
|
||||
LoginFor: FrontendLoginForOIDC,
|
||||
OIDCTicket: ticket,
|
||||
OIDCScope: req.Scope,
|
||||
OIDCName: client.Name,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -318,33 +289,6 @@ func (controller *OIDCController) authorizeComplete(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Just before returning let's set the consent cookie
|
||||
consnetUUID, err := controller.oidc.CreateConsentEntry(c, authorizeReq.ClientID, authorizeReq.Scope)
|
||||
|
||||
// If we fail to create the consent entry, we don't want to block the authorization flow,
|
||||
// but we log the error and move on without setting the cookie
|
||||
if err == nil {
|
||||
cookieDomain, err := controller.helpers.GetCookieDomain(c.Request.Context(), c.RemoteIP())
|
||||
|
||||
if err == nil {
|
||||
cookie := &http.Cookie{
|
||||
Name: controller.runtime.ConsentCookieName,
|
||||
Value: consnetUUID,
|
||||
Path: "/",
|
||||
Domain: cookieDomain,
|
||||
Expires: time.Now().Add(365 * 24 * time.Hour), // set consent cookie for 1 year
|
||||
Secure: controller.config.Auth.SecureCookie,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
}
|
||||
http.SetCookie(c.Writer, cookie)
|
||||
} else {
|
||||
controller.log.App.Error().Err(err).Msg("Failed to determine cookie domain for consent cookie")
|
||||
}
|
||||
} else {
|
||||
controller.log.App.Error().Err(err).Msg("Failed to create consent entry")
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": 200,
|
||||
"redirect_uri": fmt.Sprintf("%s?%s", authorizeReq.RedirectURI, queries.Encode()),
|
||||
|
||||
@@ -30,8 +30,6 @@ func TestOIDCController(t *testing.T) {
|
||||
|
||||
cfg, runtime := test.CreateTestConfigs(t)
|
||||
|
||||
helpers := test.CreateTestHelpers()
|
||||
|
||||
ctx := context.TODO()
|
||||
dg := ding.New(ctx)
|
||||
|
||||
@@ -833,7 +831,7 @@ func TestOIDCController(t *testing.T) {
|
||||
svc = nil
|
||||
}
|
||||
|
||||
controller.NewOIDCController(log, svc, runtime, helpers, cfg, group, &router.RouterGroup)
|
||||
controller.NewOIDCController(log, svc, runtime, group, &router.RouterGroup)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ func TestProxyController(t *testing.T) {
|
||||
|
||||
cfg, runtime := test.CreateTestConfigs(t)
|
||||
|
||||
helpers := test.CreateTestHelpers()
|
||||
|
||||
const browserUserAgent = `
|
||||
Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Mobile Safari/537.36`
|
||||
|
||||
@@ -397,7 +395,7 @@ func TestProxyController(t *testing.T) {
|
||||
Log: log,
|
||||
})
|
||||
|
||||
authService := service.NewAuthService(log, cfg, runtime, helpers, ctx, dg, nil, store, broker, nil, policyEngine)
|
||||
authService := service.NewAuthService(log, cfg, runtime, ctx, dg, nil, store, broker, nil, policyEngine)
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
|
||||
@@ -150,7 +150,7 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
Email: email,
|
||||
Provider: "local",
|
||||
TotpPending: true,
|
||||
}, c.RemoteIP())
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
controller.log.App.Error().Err(err).Str("username", req.Username).Msg("Failed to create pending TOTP session")
|
||||
@@ -195,7 +195,7 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
cookie, err := controller.auth.CreateSession(c, sessionCookie, c.RemoteIP())
|
||||
cookie, err := controller.auth.CreateSession(c, sessionCookie)
|
||||
|
||||
if err != nil {
|
||||
controller.log.App.Error().Err(err).Str("username", req.Username).Msg("Failed to create session cookie after successful login")
|
||||
@@ -246,7 +246,7 @@ func (controller *UserController) logoutHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
cookie, err := controller.auth.DeleteSession(c, uuid, c.RemoteIP())
|
||||
cookie, err := controller.auth.DeleteSession(c, uuid)
|
||||
|
||||
if err != nil {
|
||||
controller.log.App.Error().Err(err).Msg("Error deleting session on logout")
|
||||
@@ -350,7 +350,7 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
uuid, err := c.Cookie(controller.runtime.SessionCookieName)
|
||||
|
||||
if err == nil {
|
||||
_, err = controller.auth.DeleteSession(c, uuid, c.RemoteIP())
|
||||
_, err = controller.auth.DeleteSession(c, uuid)
|
||||
if err != nil {
|
||||
controller.log.App.Error().Err(err).Msg("Failed to delete pending TOTP session after successful verification")
|
||||
}
|
||||
@@ -374,7 +374,7 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
sessionCookie.Email = user.Attributes.Email
|
||||
}
|
||||
|
||||
cookie, err := controller.auth.CreateSession(c, sessionCookie, c.RemoteIP())
|
||||
cookie, err := controller.auth.CreateSession(c, sessionCookie)
|
||||
|
||||
if err != nil {
|
||||
controller.log.App.Error().Err(err).Str("username", context.GetUsername()).Msg("Failed to create session cookie after successful TOTP verification")
|
||||
@@ -424,7 +424,7 @@ func (controller *UserController) tailscaleHandler(c *gin.Context) {
|
||||
Provider: "tailscale",
|
||||
}
|
||||
|
||||
cookie, err := controller.auth.CreateSession(c, sessionCookie, c.RemoteIP())
|
||||
cookie, err := controller.auth.CreateSession(c, sessionCookie)
|
||||
|
||||
if err != nil {
|
||||
controller.log.App.Error().Err(err).Str("username", context.GetUsername()).Msg("Failed to create session cookie after successful Tailscale login")
|
||||
|
||||
@@ -29,8 +29,6 @@ func TestUserController(t *testing.T) {
|
||||
|
||||
cfg, runtime := test.CreateTestConfigs(t)
|
||||
|
||||
helpers := test.CreateTestHelpers()
|
||||
|
||||
totpCtx := func(c *gin.Context) {
|
||||
c.Set("context", &model.UserContext{
|
||||
Authenticated: false,
|
||||
@@ -420,7 +418,7 @@ func TestUserController(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
||||
authService := service.NewAuthService(log, cfg, runtime, helpers, ctx, dg, nil, store, broker, nil, policyEngine)
|
||||
authService := service.NewAuthService(log, cfg, runtime, ctx, dg, nil, store, broker, nil, policyEngine)
|
||||
|
||||
beforeEach := func() {
|
||||
// Clear failed login attempts before each test
|
||||
|
||||
@@ -206,12 +206,12 @@ func (m *ContextMiddleware) cookieAuth(ctx context.Context, uuid string, ip stri
|
||||
}
|
||||
|
||||
if !m.auth.IsEmailWhitelisted(userContext.OAuth.ID, userContext.OAuth.Email) {
|
||||
m.auth.DeleteSession(ctx, uuid, ip)
|
||||
m.auth.DeleteSession(ctx, uuid)
|
||||
return nil, nil, fmt.Errorf("email from session cookie not whitelisted: %s", userContext.OAuth.Email)
|
||||
}
|
||||
}
|
||||
|
||||
cookie, err := m.auth.RefreshSession(ctx, uuid, ip)
|
||||
cookie, err := m.auth.RefreshSession(ctx, uuid)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error refreshing session: %w", err)
|
||||
|
||||
@@ -27,8 +27,6 @@ func TestContextMiddleware(t *testing.T) {
|
||||
|
||||
cfg, runtime := test.CreateTestConfigs(t)
|
||||
|
||||
helpers := test.CreateTestHelpers()
|
||||
|
||||
basicAuthHeader := func(username, password string) string {
|
||||
return "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password))
|
||||
}
|
||||
@@ -260,7 +258,7 @@ func TestContextMiddleware(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
|
||||
authService := service.NewAuthService(log, cfg, runtime, helpers, ctx, dg, nil, store, broker, nil, policyEngine)
|
||||
authService := service.NewAuthService(log, cfg, runtime, ctx, dg, nil, store, broker, nil, policyEngine)
|
||||
|
||||
contextMiddleware := middleware.NewContextMiddleware(log, runtime, authService, broker, nil)
|
||||
|
||||
|
||||
@@ -178,16 +178,15 @@ type UIConfig struct {
|
||||
}
|
||||
|
||||
type LDAPConfig struct {
|
||||
Address string `description:"LDAP server address." yaml:"address"`
|
||||
BindDN string `description:"Bind DN for LDAP authentication." yaml:"bindDn"`
|
||||
BindPassword string `description:"Bind password for LDAP authentication." yaml:"bindPassword"`
|
||||
BindPasswordFile string `description:"Path to the Bind password." yaml:"bindPasswordFile"`
|
||||
BaseDN string `description:"Base DN for LDAP searches." yaml:"baseDn"`
|
||||
Insecure bool `description:"Allow insecure LDAP connections." yaml:"insecure"`
|
||||
SearchFilter string `description:"LDAP search filter." yaml:"searchFilter"`
|
||||
AuthCert string `description:"Certificate for mTLS authentication." yaml:"authCert"`
|
||||
AuthKey string `description:"Certificate key for mTLS authentication." yaml:"authKey"`
|
||||
GroupCacheTTL int `description:"Cache duration for LDAP group membership in seconds." yaml:"groupCacheTTL"`
|
||||
Address string `description:"LDAP server address." yaml:"address"`
|
||||
BindDN string `description:"Bind DN for LDAP authentication." yaml:"bindDn"`
|
||||
BindPassword string `description:"Bind password for LDAP authentication." yaml:"bindPassword"`
|
||||
BaseDN string `description:"Base DN for LDAP searches." yaml:"baseDn"`
|
||||
Insecure bool `description:"Allow insecure LDAP connections." yaml:"insecure"`
|
||||
SearchFilter string `description:"LDAP search filter." yaml:"searchFilter"`
|
||||
AuthCert string `description:"Certificate for mTLS authentication." yaml:"authCert"`
|
||||
AuthKey string `description:"Certificate key for mTLS authentication." yaml:"authKey"`
|
||||
GroupCacheTTL int `description:"Cache duration for LDAP group membership in seconds." yaml:"groupCacheTTL"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
|
||||
@@ -18,7 +18,8 @@ var OverrideProviders = map[string]string{
|
||||
}
|
||||
|
||||
const SessionCookieName = "tinyauth-session"
|
||||
const CSRFCookieName = "tinyauth-csrf"
|
||||
const RedirectCookieName = "tinyauth-redirect"
|
||||
const OAuthSessionCookieName = "tinyauth-oauth"
|
||||
const ConsentCookieName = "tinyauth-consent"
|
||||
|
||||
const GracefulShutdownTimeout = 5 // seconds
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package model
|
||||
|
||||
import "context"
|
||||
|
||||
type RuntimeConfig struct {
|
||||
AppURL string
|
||||
UUID string
|
||||
CookieDomain string
|
||||
SessionCookieName string
|
||||
CSRFCookieName string
|
||||
RedirectCookieName string
|
||||
OAuthSessionCookieName string
|
||||
ConsentCookieName string
|
||||
LocalUsers []LocalUser
|
||||
OAuthProviders map[string]OAuthServiceConfig
|
||||
OAuthWhitelist []string
|
||||
@@ -17,10 +16,6 @@ type RuntimeConfig struct {
|
||||
TrustedDomains []string
|
||||
}
|
||||
|
||||
type RuntimeHelpers struct {
|
||||
GetCookieDomain func(ctx context.Context, ip string) (string, error)
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
Name string `json:"name"`
|
||||
ID string `json:"id"`
|
||||
|
||||
@@ -277,78 +277,6 @@ func TestMemoryStore(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Create and get OIDC consent",
|
||||
run: func(t *testing.T, s repository.Store) {
|
||||
consent, err := s.CreateOIDCConsent(ctx, repository.CreateOIDCConsentParams{
|
||||
UUID: "uuid-1",
|
||||
ClientID: "client-1",
|
||||
Scopes: "openid profile",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "uuid-1", consent.UUID)
|
||||
assert.Equal(t, "client-1", consent.ClientID)
|
||||
assert.Equal(t, "openid profile", consent.Scopes)
|
||||
|
||||
got, err := s.GetOIDCConsentByUUID(ctx, "uuid-1")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, consent, got)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Get OIDC consent by UUID not found",
|
||||
run: func(t *testing.T, s repository.Store) {
|
||||
_, err := s.GetOIDCConsentByUUID(ctx, "missing")
|
||||
assert.ErrorIs(t, err, repository.ErrNotFound)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Create OIDC consent unique UUID constraint",
|
||||
run: func(t *testing.T, s repository.Store) {
|
||||
_, err := s.CreateOIDCConsent(ctx, repository.CreateOIDCConsentParams{UUID: "uuid-1", ClientID: "client-1", Scopes: "openid"})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.CreateOIDCConsent(ctx, repository.CreateOIDCConsentParams{UUID: "uuid-1", ClientID: "client-2", Scopes: "profile"})
|
||||
assert.ErrorContains(t, err, "UNIQUE constraint failed: oidc_consent.uuid")
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Update OIDC consent",
|
||||
run: func(t *testing.T, s repository.Store) {
|
||||
_, err := s.CreateOIDCConsent(ctx, repository.CreateOIDCConsentParams{UUID: "uuid-1", ClientID: "client-1", Scopes: "openid"})
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := s.UpdateOIDCConsent(ctx, repository.UpdateOIDCConsentParams{
|
||||
UUID: "uuid-1",
|
||||
Scopes: "profile email",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "profile email", updated.Scopes)
|
||||
|
||||
got, err := s.GetOIDCConsentByUUID(ctx, "uuid-1")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, updated, got)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Update OIDC consent not found",
|
||||
run: func(t *testing.T, s repository.Store) {
|
||||
_, err := s.UpdateOIDCConsent(ctx, repository.UpdateOIDCConsentParams{UUID: "missing"})
|
||||
assert.ErrorIs(t, err, repository.ErrNotFound)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Delete OIDC consent by UUID",
|
||||
run: func(t *testing.T, s repository.Store) {
|
||||
_, err := s.CreateOIDCConsent(ctx, repository.CreateOIDCConsentParams{UUID: "uuid-1", ClientID: "client-1", Scopes: "openid"})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, s.DeleteOIDCConsentByUUID(ctx, "uuid-1"))
|
||||
|
||||
_, err = s.GetOIDCConsentByUUID(ctx, "uuid-1")
|
||||
assert.ErrorIs(t, err, repository.ErrNotFound)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -94,47 +94,3 @@ func (s *Store) DeleteExpiredOIDCSessions(_ context.Context, arg repository.Dele
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateOIDCConsent(_ context.Context, arg repository.CreateOIDCConsentParams) (repository.OidcConsent, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if _, ok := s.oidcConsent[arg.UUID]; ok {
|
||||
return repository.OidcConsent{}, fmt.Errorf("UNIQUE constraint failed: oidc_consent.uuid")
|
||||
}
|
||||
consent := repository.OidcConsent{
|
||||
UUID: arg.UUID,
|
||||
ClientID: arg.ClientID,
|
||||
Scopes: arg.Scopes,
|
||||
}
|
||||
s.oidcConsent[arg.UUID] = consent
|
||||
return consent, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetOIDCConsentByUUID(_ context.Context, uuid string) (repository.OidcConsent, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
consent, ok := s.oidcConsent[uuid]
|
||||
if !ok {
|
||||
return repository.OidcConsent{}, repository.ErrNotFound
|
||||
}
|
||||
return consent, nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateOIDCConsent(_ context.Context, arg repository.UpdateOIDCConsentParams) (repository.OidcConsent, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
consent, ok := s.oidcConsent[arg.UUID]
|
||||
if !ok {
|
||||
return repository.OidcConsent{}, repository.ErrNotFound
|
||||
}
|
||||
consent.Scopes = arg.Scopes
|
||||
s.oidcConsent[arg.UUID] = consent
|
||||
return consent, nil
|
||||
}
|
||||
|
||||
func (s *Store) DeleteOIDCConsentByUUID(_ context.Context, uuid string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
delete(s.oidcConsent, uuid)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ type Store struct {
|
||||
mu sync.RWMutex
|
||||
sessions map[string]repository.Session
|
||||
oidcSessions map[string]repository.OidcSession
|
||||
oidcConsent map[string]repository.OidcConsent
|
||||
}
|
||||
|
||||
// New returns a new empty in-memory Store.
|
||||
@@ -20,6 +19,5 @@ func New() repository.Store {
|
||||
return &Store{
|
||||
sessions: make(map[string]repository.Session),
|
||||
oidcSessions: make(map[string]repository.OidcSession),
|
||||
oidcConsent: make(map[string]repository.OidcConsent),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
package repository
|
||||
|
||||
import "time"
|
||||
|
||||
// Shared model and parameter types for all storage drivers.
|
||||
// sqlc-generated driver packages use these via the conversion layer in their store.go.
|
||||
|
||||
type OidcConsent struct {
|
||||
UUID string
|
||||
ClientID string
|
||||
Scopes string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
UUID string
|
||||
Username string
|
||||
@@ -94,14 +84,3 @@ type DeleteExpiredOIDCSessionsParams struct {
|
||||
TokenExpiresAt int64
|
||||
RefreshTokenExpiresAt int64
|
||||
}
|
||||
|
||||
type CreateOIDCConsentParams struct {
|
||||
UUID string
|
||||
ClientID string
|
||||
Scopes string
|
||||
}
|
||||
|
||||
type UpdateOIDCConsentParams struct {
|
||||
Scopes string
|
||||
UUID string
|
||||
}
|
||||
|
||||
@@ -4,18 +4,6 @@
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type OidcConsent struct {
|
||||
UUID string
|
||||
ClientID string
|
||||
Scopes string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type OidcSession struct {
|
||||
Sub string
|
||||
AccessTokenHash string
|
||||
|
||||
@@ -9,36 +9,6 @@ import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const createOIDCConsent = `-- name: CreateOIDCConsent :one
|
||||
INSERT INTO "oidc_consent" (
|
||||
"uuid",
|
||||
"client_id",
|
||||
"scopes"
|
||||
) VALUES (
|
||||
$1, $2, $3
|
||||
)
|
||||
RETURNING uuid, client_id, scopes, created_at, updated_at
|
||||
`
|
||||
|
||||
type CreateOIDCConsentParams struct {
|
||||
UUID string
|
||||
ClientID string
|
||||
Scopes string
|
||||
}
|
||||
|
||||
func (q *Queries) CreateOIDCConsent(ctx context.Context, arg CreateOIDCConsentParams) (OidcConsent, error) {
|
||||
row := q.db.QueryRowContext(ctx, createOIDCConsent, arg.UUID, arg.ClientID, arg.Scopes)
|
||||
var i OidcConsent
|
||||
err := row.Scan(
|
||||
&i.UUID,
|
||||
&i.ClientID,
|
||||
&i.Scopes,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const createOIDCSession = `-- name: CreateOIDCSession :one
|
||||
INSERT INTO "oidc_sessions" (
|
||||
"sub",
|
||||
@@ -110,16 +80,6 @@ func (q *Queries) DeleteExpiredOIDCSessions(ctx context.Context, arg DeleteExpir
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteOIDCConsentByUUID = `-- name: DeleteOIDCConsentByUUID :exec
|
||||
DELETE FROM "oidc_consent"
|
||||
WHERE "uuid" = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteOIDCConsentByUUID(ctx context.Context, uuid string) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteOIDCConsentByUUID, uuid)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteOIDCSessionBySub = `-- name: DeleteOIDCSessionBySub :exec
|
||||
DELETE FROM "oidc_sessions"
|
||||
WHERE "sub" = $1
|
||||
@@ -130,24 +90,6 @@ func (q *Queries) DeleteOIDCSessionBySub(ctx context.Context, sub string) error
|
||||
return err
|
||||
}
|
||||
|
||||
const getOIDCConsentByUUID = `-- name: GetOIDCConsentByUUID :one
|
||||
SELECT uuid, client_id, scopes, created_at, updated_at FROM "oidc_consent"
|
||||
WHERE "uuid" = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetOIDCConsentByUUID(ctx context.Context, uuid string) (OidcConsent, error) {
|
||||
row := q.db.QueryRowContext(ctx, getOIDCConsentByUUID, uuid)
|
||||
var i OidcConsent
|
||||
err := row.Scan(
|
||||
&i.UUID,
|
||||
&i.ClientID,
|
||||
&i.Scopes,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getOIDCSessionByAccessTokenHash = `-- name: GetOIDCSessionByAccessTokenHash :one
|
||||
SELECT sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at, nonce, userinfo_json FROM "oidc_sessions"
|
||||
WHERE "access_token_hash" = $1
|
||||
@@ -214,32 +156,6 @@ func (q *Queries) GetOIDCSessionBySub(ctx context.Context, sub string) (OidcSess
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateOIDCConsent = `-- name: UpdateOIDCConsent :one
|
||||
UPDATE "oidc_consent" SET
|
||||
"scopes" = $1,
|
||||
"updated_at" = CURRENT_TIMESTAMP
|
||||
WHERE "uuid" = $2
|
||||
RETURNING uuid, client_id, scopes, created_at, updated_at
|
||||
`
|
||||
|
||||
type UpdateOIDCConsentParams struct {
|
||||
Scopes string
|
||||
UUID string
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateOIDCConsent(ctx context.Context, arg UpdateOIDCConsentParams) (OidcConsent, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateOIDCConsent, arg.Scopes, arg.UUID)
|
||||
var i OidcConsent
|
||||
err := row.Scan(
|
||||
&i.UUID,
|
||||
&i.ClientID,
|
||||
&i.Scopes,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateOIDCSession = `-- name: UpdateOIDCSession :one
|
||||
UPDATE "oidc_sessions" SET
|
||||
"access_token_hash" = $1,
|
||||
|
||||
@@ -32,14 +32,6 @@ func mapErr(err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) CreateOIDCConsent(ctx context.Context, arg repository.CreateOIDCConsentParams) (repository.OidcConsent, error) {
|
||||
r, err := s.q.CreateOIDCConsent(ctx, CreateOIDCConsentParams(arg))
|
||||
if err != nil {
|
||||
return repository.OidcConsent{}, mapErr(err)
|
||||
}
|
||||
return repository.OidcConsent(r), nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateOIDCSession(ctx context.Context, arg repository.CreateOIDCSessionParams) (repository.OidcSession, error) {
|
||||
r, err := s.q.CreateOIDCSession(ctx, CreateOIDCSessionParams(arg))
|
||||
if err != nil {
|
||||
@@ -64,10 +56,6 @@ func (s *Store) DeleteExpiredSessions(ctx context.Context, expiry int64) error {
|
||||
return mapErr(s.q.DeleteExpiredSessions(ctx, expiry))
|
||||
}
|
||||
|
||||
func (s *Store) DeleteOIDCConsentByUUID(ctx context.Context, uuid string) error {
|
||||
return mapErr(s.q.DeleteOIDCConsentByUUID(ctx, uuid))
|
||||
}
|
||||
|
||||
func (s *Store) DeleteOIDCSessionBySub(ctx context.Context, sub string) error {
|
||||
return mapErr(s.q.DeleteOIDCSessionBySub(ctx, sub))
|
||||
}
|
||||
@@ -76,14 +64,6 @@ func (s *Store) DeleteSession(ctx context.Context, uuid string) error {
|
||||
return mapErr(s.q.DeleteSession(ctx, uuid))
|
||||
}
|
||||
|
||||
func (s *Store) GetOIDCConsentByUUID(ctx context.Context, uuid string) (repository.OidcConsent, error) {
|
||||
r, err := s.q.GetOIDCConsentByUUID(ctx, uuid)
|
||||
if err != nil {
|
||||
return repository.OidcConsent{}, mapErr(err)
|
||||
}
|
||||
return repository.OidcConsent(r), nil
|
||||
}
|
||||
|
||||
func (s *Store) GetOIDCSessionByAccessTokenHash(ctx context.Context, accessTokenHash string) (repository.OidcSession, error) {
|
||||
r, err := s.q.GetOIDCSessionByAccessTokenHash(ctx, accessTokenHash)
|
||||
if err != nil {
|
||||
@@ -116,14 +96,6 @@ func (s *Store) GetSession(ctx context.Context, uuid string) (repository.Session
|
||||
return repository.Session(r), nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateOIDCConsent(ctx context.Context, arg repository.UpdateOIDCConsentParams) (repository.OidcConsent, error) {
|
||||
r, err := s.q.UpdateOIDCConsent(ctx, UpdateOIDCConsentParams(arg))
|
||||
if err != nil {
|
||||
return repository.OidcConsent{}, mapErr(err)
|
||||
}
|
||||
return repository.OidcConsent(r), nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateOIDCSession(ctx context.Context, arg repository.UpdateOIDCSessionParams) (repository.OidcSession, error) {
|
||||
r, err := s.q.UpdateOIDCSession(ctx, UpdateOIDCSessionParams(arg))
|
||||
if err != nil {
|
||||
|
||||
@@ -4,18 +4,6 @@
|
||||
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type OidcConsent struct {
|
||||
UUID string
|
||||
ClientID string
|
||||
Scopes string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type OidcSession struct {
|
||||
Sub string
|
||||
AccessTokenHash string
|
||||
|
||||
@@ -9,36 +9,6 @@ import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const createOIDCConsent = `-- name: CreateOIDCConsent :one
|
||||
INSERT INTO "oidc_consent" (
|
||||
"uuid",
|
||||
"client_id",
|
||||
"scopes"
|
||||
) VALUES (
|
||||
?, ?, ?
|
||||
)
|
||||
RETURNING uuid, client_id, scopes, created_at, updated_at
|
||||
`
|
||||
|
||||
type CreateOIDCConsentParams struct {
|
||||
UUID string
|
||||
ClientID string
|
||||
Scopes string
|
||||
}
|
||||
|
||||
func (q *Queries) CreateOIDCConsent(ctx context.Context, arg CreateOIDCConsentParams) (OidcConsent, error) {
|
||||
row := q.db.QueryRowContext(ctx, createOIDCConsent, arg.UUID, arg.ClientID, arg.Scopes)
|
||||
var i OidcConsent
|
||||
err := row.Scan(
|
||||
&i.UUID,
|
||||
&i.ClientID,
|
||||
&i.Scopes,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const createOIDCSession = `-- name: CreateOIDCSession :one
|
||||
INSERT INTO "oidc_sessions" (
|
||||
"sub",
|
||||
@@ -110,16 +80,6 @@ func (q *Queries) DeleteExpiredOIDCSessions(ctx context.Context, arg DeleteExpir
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteOIDCConsentByUUID = `-- name: DeleteOIDCConsentByUUID :exec
|
||||
DELETE FROM "oidc_consent"
|
||||
WHERE "uuid" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteOIDCConsentByUUID(ctx context.Context, uuid string) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteOIDCConsentByUUID, uuid)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteOIDCSessionBySub = `-- name: DeleteOIDCSessionBySub :exec
|
||||
DELETE FROM "oidc_sessions"
|
||||
WHERE "sub" = ?
|
||||
@@ -130,24 +90,6 @@ func (q *Queries) DeleteOIDCSessionBySub(ctx context.Context, sub string) error
|
||||
return err
|
||||
}
|
||||
|
||||
const getOIDCConsentByUUID = `-- name: GetOIDCConsentByUUID :one
|
||||
SELECT uuid, client_id, scopes, created_at, updated_at FROM "oidc_consent"
|
||||
WHERE "uuid" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) GetOIDCConsentByUUID(ctx context.Context, uuid string) (OidcConsent, error) {
|
||||
row := q.db.QueryRowContext(ctx, getOIDCConsentByUUID, uuid)
|
||||
var i OidcConsent
|
||||
err := row.Scan(
|
||||
&i.UUID,
|
||||
&i.ClientID,
|
||||
&i.Scopes,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getOIDCSessionByAccessTokenHash = `-- name: GetOIDCSessionByAccessTokenHash :one
|
||||
SELECT sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at, nonce, userinfo_json FROM "oidc_sessions"
|
||||
WHERE "access_token_hash" = ?
|
||||
@@ -214,32 +156,6 @@ func (q *Queries) GetOIDCSessionBySub(ctx context.Context, sub string) (OidcSess
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateOIDCConsent = `-- name: UpdateOIDCConsent :one
|
||||
UPDATE "oidc_consent" SET
|
||||
"scopes" = ?,
|
||||
"updated_at" = CURRENT_TIMESTAMP
|
||||
WHERE "uuid" = ?
|
||||
RETURNING uuid, client_id, scopes, created_at, updated_at
|
||||
`
|
||||
|
||||
type UpdateOIDCConsentParams struct {
|
||||
Scopes string
|
||||
UUID string
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateOIDCConsent(ctx context.Context, arg UpdateOIDCConsentParams) (OidcConsent, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateOIDCConsent, arg.Scopes, arg.UUID)
|
||||
var i OidcConsent
|
||||
err := row.Scan(
|
||||
&i.UUID,
|
||||
&i.ClientID,
|
||||
&i.Scopes,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateOIDCSession = `-- name: UpdateOIDCSession :one
|
||||
UPDATE "oidc_sessions" SET
|
||||
"access_token_hash" = ?,
|
||||
|
||||
@@ -32,14 +32,6 @@ func mapErr(err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) CreateOIDCConsent(ctx context.Context, arg repository.CreateOIDCConsentParams) (repository.OidcConsent, error) {
|
||||
r, err := s.q.CreateOIDCConsent(ctx, CreateOIDCConsentParams(arg))
|
||||
if err != nil {
|
||||
return repository.OidcConsent{}, mapErr(err)
|
||||
}
|
||||
return repository.OidcConsent(r), nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateOIDCSession(ctx context.Context, arg repository.CreateOIDCSessionParams) (repository.OidcSession, error) {
|
||||
r, err := s.q.CreateOIDCSession(ctx, CreateOIDCSessionParams(arg))
|
||||
if err != nil {
|
||||
@@ -64,10 +56,6 @@ func (s *Store) DeleteExpiredSessions(ctx context.Context, expiry int64) error {
|
||||
return mapErr(s.q.DeleteExpiredSessions(ctx, expiry))
|
||||
}
|
||||
|
||||
func (s *Store) DeleteOIDCConsentByUUID(ctx context.Context, uuid string) error {
|
||||
return mapErr(s.q.DeleteOIDCConsentByUUID(ctx, uuid))
|
||||
}
|
||||
|
||||
func (s *Store) DeleteOIDCSessionBySub(ctx context.Context, sub string) error {
|
||||
return mapErr(s.q.DeleteOIDCSessionBySub(ctx, sub))
|
||||
}
|
||||
@@ -76,14 +64,6 @@ func (s *Store) DeleteSession(ctx context.Context, uuid string) error {
|
||||
return mapErr(s.q.DeleteSession(ctx, uuid))
|
||||
}
|
||||
|
||||
func (s *Store) GetOIDCConsentByUUID(ctx context.Context, uuid string) (repository.OidcConsent, error) {
|
||||
r, err := s.q.GetOIDCConsentByUUID(ctx, uuid)
|
||||
if err != nil {
|
||||
return repository.OidcConsent{}, mapErr(err)
|
||||
}
|
||||
return repository.OidcConsent(r), nil
|
||||
}
|
||||
|
||||
func (s *Store) GetOIDCSessionByAccessTokenHash(ctx context.Context, accessTokenHash string) (repository.OidcSession, error) {
|
||||
r, err := s.q.GetOIDCSessionByAccessTokenHash(ctx, accessTokenHash)
|
||||
if err != nil {
|
||||
@@ -116,14 +96,6 @@ func (s *Store) GetSession(ctx context.Context, uuid string) (repository.Session
|
||||
return repository.Session(r), nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateOIDCConsent(ctx context.Context, arg repository.UpdateOIDCConsentParams) (repository.OidcConsent, error) {
|
||||
r, err := s.q.UpdateOIDCConsent(ctx, UpdateOIDCConsentParams(arg))
|
||||
if err != nil {
|
||||
return repository.OidcConsent{}, mapErr(err)
|
||||
}
|
||||
return repository.OidcConsent(r), nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateOIDCSession(ctx context.Context, arg repository.UpdateOIDCSessionParams) (repository.OidcSession, error) {
|
||||
r, err := s.q.UpdateOIDCSession(ctx, UpdateOIDCSessionParams(arg))
|
||||
if err != nil {
|
||||
|
||||
@@ -27,10 +27,4 @@ type Store interface {
|
||||
GetOIDCSessionByRefreshTokenHash(ctx context.Context, refreshTokenHash string) (OidcSession, error)
|
||||
GetOIDCSessionBySub(ctx context.Context, sub string) (OidcSession, error)
|
||||
UpdateOIDCSession(ctx context.Context, arg UpdateOIDCSessionParams) (OidcSession, error)
|
||||
|
||||
// OIDC consents
|
||||
CreateOIDCConsent(ctx context.Context, arg CreateOIDCConsentParams) (OidcConsent, error)
|
||||
DeleteOIDCConsentByUUID(ctx context.Context, uuid string) error
|
||||
GetOIDCConsentByUUID(ctx context.Context, uuid string) (OidcConsent, error)
|
||||
UpdateOIDCConsent(ctx context.Context, arg UpdateOIDCConsentParams) (OidcConsent, error)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,6 @@ type AuthService struct {
|
||||
log *logger.Logger
|
||||
config model.Config
|
||||
runtime model.RuntimeConfig
|
||||
helpers *model.RuntimeHelpers
|
||||
ctx context.Context
|
||||
|
||||
ldap *LdapService
|
||||
@@ -87,7 +86,6 @@ func NewAuthService(
|
||||
log *logger.Logger,
|
||||
config model.Config,
|
||||
runtime model.RuntimeConfig,
|
||||
helpers *model.RuntimeHelpers,
|
||||
ctx context.Context,
|
||||
dg *ding.Ding,
|
||||
ldap *LdapService,
|
||||
@@ -99,7 +97,6 @@ func NewAuthService(
|
||||
service := &AuthService{
|
||||
log: log,
|
||||
runtime: runtime,
|
||||
helpers: helpers,
|
||||
ctx: ctx,
|
||||
config: config,
|
||||
ldap: ldap,
|
||||
@@ -325,7 +322,7 @@ func (auth *AuthService) IsEmailWhitelisted(provider string, email string) bool
|
||||
})
|
||||
}
|
||||
|
||||
func (auth *AuthService) CreateSession(ctx context.Context, data repository.Session, ip string) (*http.Cookie, error) {
|
||||
func (auth *AuthService) CreateSession(ctx context.Context, data repository.Session) (*http.Cookie, error) {
|
||||
if data.Provider == "tailscale" && auth.tailscale == nil {
|
||||
return nil, fmt.Errorf("tailscale service not configured, cannot create session for tailscale user")
|
||||
}
|
||||
@@ -366,17 +363,33 @@ func (auth *AuthService) CreateSession(ctx context.Context, data repository.Sess
|
||||
return nil, fmt.Errorf("failed to create session entry: %w", err)
|
||||
}
|
||||
|
||||
cookieDomain, err := auth.helpers.GetCookieDomain(ctx, ip)
|
||||
if data.Provider == "tailscale" {
|
||||
auth.log.App.Trace().Str("url", fmt.Sprintf("https://%s", auth.tailscale.GetHostname())).Msg("Extracting root domain from Tailscale hostname")
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine cookie domain: %w", err)
|
||||
tsCookieDomain, err := utils.GetCookieDomain(fmt.Sprintf("https://%s", auth.tailscale.GetHostname()))
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get cookie domain for tailscale user: %w", err)
|
||||
}
|
||||
|
||||
return &http.Cookie{
|
||||
Name: auth.runtime.SessionCookieName,
|
||||
Value: session.UUID,
|
||||
Path: "/",
|
||||
Domain: fmt.Sprintf(".%s", tsCookieDomain),
|
||||
Expires: expiresAt,
|
||||
MaxAge: int(time.Until(expiresAt).Seconds()),
|
||||
Secure: auth.config.Auth.SecureCookie,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &http.Cookie{
|
||||
Name: auth.runtime.SessionCookieName,
|
||||
Value: session.UUID,
|
||||
Path: "/",
|
||||
Domain: cookieDomain,
|
||||
Domain: fmt.Sprintf(".%s", auth.runtime.CookieDomain),
|
||||
Expires: expiresAt,
|
||||
MaxAge: int(time.Until(expiresAt).Seconds()),
|
||||
Secure: auth.config.Auth.SecureCookie,
|
||||
@@ -385,17 +398,13 @@ func (auth *AuthService) CreateSession(ctx context.Context, data repository.Sess
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (auth *AuthService) RefreshSession(ctx context.Context, uuid string, ip string) (*http.Cookie, error) {
|
||||
func (auth *AuthService) RefreshSession(ctx context.Context, uuid string) (*http.Cookie, error) {
|
||||
session, err := auth.queries.GetSession(ctx, uuid)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve session: %w", err)
|
||||
}
|
||||
|
||||
if session.Provider == "tailscale" && auth.tailscale == nil {
|
||||
return nil, fmt.Errorf("tailscale service not configured, cannot create session for tailscale user")
|
||||
}
|
||||
|
||||
currentTime := time.Now().Unix()
|
||||
|
||||
var refreshThreshold int64
|
||||
@@ -429,17 +438,11 @@ func (auth *AuthService) RefreshSession(ctx context.Context, uuid string, ip str
|
||||
return nil, fmt.Errorf("failed to update session expiry: %w", err)
|
||||
}
|
||||
|
||||
cookieDomain, err := auth.helpers.GetCookieDomain(ctx, ip)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine cookie domain: %w", err)
|
||||
}
|
||||
|
||||
return &http.Cookie{
|
||||
Name: auth.runtime.SessionCookieName,
|
||||
Value: session.UUID,
|
||||
Path: "/",
|
||||
Domain: cookieDomain,
|
||||
Domain: fmt.Sprintf(".%s", auth.runtime.CookieDomain),
|
||||
Expires: time.Now().Add(time.Duration(newExpiry-currentTime) * time.Second),
|
||||
MaxAge: int(newExpiry - currentTime),
|
||||
Secure: auth.config.Auth.SecureCookie,
|
||||
@@ -449,24 +452,18 @@ func (auth *AuthService) RefreshSession(ctx context.Context, uuid string, ip str
|
||||
|
||||
}
|
||||
|
||||
func (auth *AuthService) DeleteSession(ctx context.Context, uuid string, ip string) (*http.Cookie, error) {
|
||||
func (auth *AuthService) DeleteSession(ctx context.Context, uuid string) (*http.Cookie, error) {
|
||||
err := auth.queries.DeleteSession(ctx, uuid)
|
||||
|
||||
if err != nil {
|
||||
auth.log.App.Error().Err(err).Str("uuid", uuid).Msg("Failed to delete session from database")
|
||||
}
|
||||
|
||||
cookieDomain, err := auth.helpers.GetCookieDomain(ctx, ip)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to determine cookie domain: %w", err)
|
||||
}
|
||||
|
||||
return &http.Cookie{
|
||||
Name: auth.runtime.SessionCookieName,
|
||||
Value: "",
|
||||
Path: "/",
|
||||
Domain: cookieDomain,
|
||||
Domain: fmt.Sprintf(".%s", auth.runtime.CookieDomain),
|
||||
Expires: time.Now(),
|
||||
MaxAge: -1,
|
||||
Secure: auth.config.Auth.SecureCookie,
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
ldapgo "github.com/go-ldap/ldap/v3"
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
)
|
||||
|
||||
@@ -33,10 +32,6 @@ func NewLdapService(
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
secret := utils.GetSecret(config.LDAP.BindPassword, config.LDAP.BindPasswordFile)
|
||||
config.LDAP.BindPassword = secret
|
||||
config.LDAP.BindPasswordFile = ""
|
||||
|
||||
ldap := &LdapService{
|
||||
log: log,
|
||||
config: config,
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
|
||||
"github.com/go-jose/go-jose/v4"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
@@ -108,7 +107,6 @@ type TokenResponse struct {
|
||||
}
|
||||
|
||||
type AuthorizeRequest struct {
|
||||
jwt.Claims
|
||||
Scope string `form:"scope" json:"scope" url:"scope"`
|
||||
ResponseType string `form:"response_type" json:"response_type" url:"response_type"`
|
||||
ClientID string `form:"client_id" json:"client_id" url:"client_id"`
|
||||
@@ -889,63 +887,32 @@ func (service *OIDCService) DeleteAuthorizeRequestTicket(ticket string) {
|
||||
|
||||
// TODO: support signed request objects in the future
|
||||
func (service *OIDCService) DecodeAuthorizeJWT(tokenString string) (*AuthorizeRequest, error) {
|
||||
var req AuthorizeRequest
|
||||
|
||||
token, _, err := jwt.NewParser().ParseUnverified(tokenString, &req)
|
||||
var claims jwt.MapClaims
|
||||
|
||||
token, _, err := jwt.NewParser().ParseUnverified(tokenString, &claims)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse authorize request jwt: %w", err)
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(*AuthorizeRequest)
|
||||
alg, ok := token.Header["alg"].(string)
|
||||
|
||||
if !ok {
|
||||
return nil, errors.New("failed to parse claims from authorize request jwt")
|
||||
if !ok || alg != "none" || string(token.Signature) != "" {
|
||||
return nil, fmt.Errorf("only unsigned jwts are supported for authorize requests")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (service *OIDCService) CreateConsentEntry(ctx context.Context, clientId string, scope string) (string, error) {
|
||||
u := uuid.New()
|
||||
|
||||
entry := repository.CreateOIDCConsentParams{
|
||||
UUID: u.String(),
|
||||
ClientID: clientId,
|
||||
Scopes: scope,
|
||||
get := func(k string) string {
|
||||
v, _ := claims[k].(string)
|
||||
return v
|
||||
}
|
||||
|
||||
_, err := service.queries.CreateOIDCConsent(ctx, entry)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return entry.UUID, nil
|
||||
}
|
||||
|
||||
func (service *OIDCService) GetConsentEntry(ctx context.Context, uuid string) (*repository.OidcConsent, error) {
|
||||
entry, err := service.queries.GetOIDCConsentByUUID(ctx, uuid)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entry, nil
|
||||
}
|
||||
|
||||
func (service *OIDCService) DeleteConsentEntry(ctx context.Context, uuid string) error {
|
||||
return service.queries.DeleteOIDCConsentByUUID(ctx, uuid)
|
||||
}
|
||||
|
||||
func (service *OIDCService) UpdateConsentEntry(ctx context.Context, uuid string, scopes string) error {
|
||||
_, err := service.queries.UpdateOIDCConsent(ctx, repository.UpdateOIDCConsentParams{
|
||||
UUID: uuid,
|
||||
Scopes: scopes,
|
||||
})
|
||||
|
||||
return err
|
||||
return &AuthorizeRequest{
|
||||
Scope: get("scope"),
|
||||
ResponseType: get("response_type"),
|
||||
ClientID: get("client_id"),
|
||||
RedirectURI: get("redirect_uri"),
|
||||
State: get("state"),
|
||||
Nonce: get("nonce"),
|
||||
CodeChallenge: get("code_challenge"),
|
||||
CodeChallengeMethod: get("code_challenge_method"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@@ -134,11 +133,3 @@ func CreateTestConfigs(t *testing.T) (model.Config, model.RuntimeConfig) {
|
||||
|
||||
return config, runtime
|
||||
}
|
||||
|
||||
func CreateTestHelpers() *model.RuntimeHelpers {
|
||||
return &model.RuntimeHelpers{
|
||||
GetCookieDomain: func(ctx context.Context, ip string) (string, error) {
|
||||
return "example.com", nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,28 +46,3 @@ UPDATE "oidc_sessions" SET
|
||||
"userinfo_json" = $8
|
||||
WHERE "sub" = $9
|
||||
RETURNING *;
|
||||
|
||||
-- name: CreateOIDCConsent :one
|
||||
INSERT INTO "oidc_consent" (
|
||||
"uuid",
|
||||
"client_id",
|
||||
"scopes"
|
||||
) VALUES (
|
||||
$1, $2, $3
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetOIDCConsentByUUID :one
|
||||
SELECT * FROM "oidc_consent"
|
||||
WHERE "uuid" = $1;
|
||||
|
||||
-- name: UpdateOIDCConsent :one
|
||||
UPDATE "oidc_consent" SET
|
||||
"scopes" = $1,
|
||||
"updated_at" = CURRENT_TIMESTAMP
|
||||
WHERE "uuid" = $2
|
||||
RETURNING *;
|
||||
|
||||
-- name: DeleteOIDCConsentByUUID :exec
|
||||
DELETE FROM "oidc_consent"
|
||||
WHERE "uuid" = $1;
|
||||
|
||||
@@ -9,11 +9,3 @@ CREATE TABLE IF NOT EXISTS "oidc_sessions" (
|
||||
"nonce" TEXT NOT NULL DEFAULT '',
|
||||
"userinfo_json" TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "oidc_consent" (
|
||||
"uuid" TEXT NOT NULL UNIQUE PRIMARY KEY,
|
||||
"client_id" TEXT NOT NULL,
|
||||
"scopes" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
@@ -46,28 +46,3 @@ UPDATE "oidc_sessions" SET
|
||||
"userinfo_json" = ?
|
||||
WHERE "sub" = ?
|
||||
RETURNING *;
|
||||
|
||||
-- name: CreateOIDCConsent :one
|
||||
INSERT INTO "oidc_consent" (
|
||||
"uuid",
|
||||
"client_id",
|
||||
"scopes"
|
||||
) VALUES (
|
||||
?, ?, ?
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetOIDCConsentByUUID :one
|
||||
SELECT * FROM "oidc_consent"
|
||||
WHERE "uuid" = ?;
|
||||
|
||||
-- name: UpdateOIDCConsent :one
|
||||
UPDATE "oidc_consent" SET
|
||||
"scopes" = ?,
|
||||
"updated_at" = CURRENT_TIMESTAMP
|
||||
WHERE "uuid" = ?
|
||||
RETURNING *;
|
||||
|
||||
-- name: DeleteOIDCConsentByUUID :exec
|
||||
DELETE FROM "oidc_consent"
|
||||
WHERE "uuid" = ?;
|
||||
|
||||
@@ -9,11 +9,3 @@ CREATE TABLE IF NOT EXISTS "oidc_sessions" (
|
||||
"nonce" TEXT NOT NULL DEFAULT "",
|
||||
"userinfo_json" TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "oidc_consent" (
|
||||
"uuid" TEXT NOT NULL UNIQUE PRIMARY KEY,
|
||||
"client_id" TEXT NOT NULL,
|
||||
"scopes" TEXT NOT NULL,
|
||||
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user