diff --git a/browser-ext/src/background/background.ts b/browser-ext/src/background/background.ts index 5ec28df0..9cf088e9 100644 --- a/browser-ext/src/background/background.ts +++ b/browser-ext/src/background/background.ts @@ -3,25 +3,48 @@ import { sentry } from "../utils/sentry"; sentry("background"); -chrome.runtime.onInstalled.addListener(() => { - console.debug("authentik Extension Installed"); -}); +const browserApi = (globalThis as typeof globalThis & { browser?: typeof chrome }).browser; +const runtimeApi = browserApi?.runtime ?? chrome.runtime; + +function stringifyError(exc: unknown): string { + if (exc instanceof Error) { + return exc.message; + } + return String(exc); +} const native = new Native(); -chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { +async function handleMessage(msg: { action?: string; profile?: string; challenge?: string }) { switch (msg.action) { case "platform_sign_endpoint_header": - native - .platformSignEndpointHeader(msg.profile, msg.challenge) - .then((r) => { - sendResponse(r); - }) - .catch((exc) => { - console.warn("Failed to send request for platform sign", exc); - sendResponse(null); - }); - break; + try { + return await native.platformSignEndpointHeader( + msg.profile ?? "default", + msg.challenge ?? "", + ); + } catch (exc) { + console.warn("Failed to send request for platform sign", exc); + return { + error: stringifyError(exc), + }; + } + default: + return false; } - return true; -}); +} + +runtimeApi.onMessage.addListener( + ( + msg: { action?: string; profile?: string; challenge?: string }, + _sender: unknown, + sendResponse: (response: unknown) => void, + ) => { + const response = handleMessage(msg); + if (browserApi?.runtime) { + return response; + } + response.then(sendResponse); + return true; + }, +); diff --git a/browser-ext/src/content/content.ts b/browser-ext/src/content/content.ts index 28d3d65a..2caf57ed 100644 --- a/browser-ext/src/content/content.ts +++ b/browser-ext/src/content/content.ts @@ -1,3 +1,41 @@ +function stringifyError(value: unknown): string | null { + if (value && typeof value === "object" && "error" in value) { + const err = (value as { error?: unknown }).error; + if (typeof err === "string") { + return err; + } + } + return null; +} + +const browserApi = (globalThis as typeof globalThis & { browser?: typeof chrome }).browser; +const runtimeApi = browserApi?.runtime ?? chrome.runtime; + +function sendRuntimeMessage(message: { + action: string; + profile: string; + challenge: string; +}): Promise { + if (browserApi?.runtime) { + return runtimeApi.sendMessage(message); + } + return new Promise((resolve, reject) => { + try { + runtimeApi.sendMessage(message, (response: unknown) => { + const lastError = + typeof chrome !== "undefined" ? chrome.runtime?.lastError : undefined; + if (lastError) { + reject(new Error(lastError.message)); + return; + } + resolve(response); + }); + } catch (exc) { + reject(exc); + } + }); +} + window.addEventListener( "message", (event) => { @@ -11,19 +49,34 @@ window.addEventListener( if (event.source !== window) { return; } - chrome.runtime - .sendMessage({ - action: "platform_sign_endpoint_header", - profile: "default", - challenge: event.data.challenge, - }) + sendRuntimeMessage({ + action: "platform_sign_endpoint_header", + profile: "default", + challenge: event.data.challenge, + }) .then((signed) => { + const error = stringifyError(signed); + if (error) { + console.warn( + "authentik/bext: failed to sign endpoint challenge", + error, + ); + return; + } if (signed) { - window.postMessage({ - _ak_ext: "authentik-platform-sso", - response: signed, - }); + window.postMessage( + { + _ak_ext: "authentik-platform-sso", + response: signed, + }, + window.location.origin, + ); + } else { + console.warn("authentik/bext: background returned empty response"); } + }) + .catch((exc) => { + console.warn("authentik/bext: background request failed", exc); }); } catch (exc) { console.warn(`authentik/bext: ${exc}`); diff --git a/browser-ext/src/utils/native.ts b/browser-ext/src/utils/native.ts index d5a8ee4d..21adc7f0 100644 --- a/browser-ext/src/utils/native.ts +++ b/browser-ext/src/utils/native.ts @@ -11,8 +11,12 @@ export interface Message { export interface Response { response_to: string; data: { [key: string]: unknown }; + error?: string; } +const browserApi = (globalThis as typeof globalThis & { browser?: typeof chrome }).browser; +const runtimeApi = browserApi?.runtime ?? chrome.runtime; + function createRandomString(length: number = 16) { const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let result = ""; @@ -25,60 +29,121 @@ function createRandomString(length: number = 16) { } const defaultReconnectDelay = 5; +const requestTimeoutMs = 2500; + +type PendingRequest = PromiseWithResolvers & { + timeout?: ReturnType; +}; export class Native { #port?: chrome.runtime.Port; - #promises: Map> = new Map(); + #promises: Map = new Map(); #reconnectDelay = defaultReconnectDelay; #reconnectTimeout = 0; + #isConnected = false; constructor() { this.#connect(); } #connect() { - this.#port = chrome.runtime.connectNative("io.goauthentik.platform"); - this.#port.onMessage.addListener(this.#listener.bind(this)); - this.#port.onDisconnect.addListener(() => { + const port = runtimeApi.connectNative("io.goauthentik.platform"); + this.#port = port; + this.#isConnected = true; + this.#reconnectDelay = defaultReconnectDelay; + port.onMessage.addListener(this.#listener.bind(this)); + port.onDisconnect.addListener(() => { + this.#isConnected = false; this.#reconnectDelay *= 1.35; this.#reconnectDelay = Math.min(this.#reconnectDelay, 3600); - // @ts-ignore - const err = chrome.runtime.lastError || this.#port?.error; - console.debug( - `authentik/bext/native: Disconnected, reconnecting in ${this.#reconnectDelay}`, - err, + const err = + (typeof chrome !== "undefined" ? chrome.runtime?.lastError : undefined) || + (port as chrome.runtime.Port & { error?: unknown }).error; + this.#rejectPending( + new Error(`native host disconnected${err ? `: ${String(err)}` : ""}`), ); + this.#port = undefined; clearTimeout(this.#reconnectTimeout); this.#reconnectTimeout = setTimeout(() => { this.#connect(); }, this.#reconnectDelay * 1000); }); - console.debug("authentik/bext/native: Connected to native"); } #listener(msg: Response) { const prom = this.#promises.get(msg.response_to); - console.debug(`authentik/bext/native[${msg.response_to}]: Got response`); if (!prom) { - console.debug(`authentik/bext/native[${msg.response_to}]: No promise to resolve`); return; } + if (msg.error) { + if (prom.timeout) { + clearTimeout(prom.timeout); + } + prom.reject(new Error(msg.error)); + this.#promises.delete(msg.response_to); + return; + } + if (prom.timeout) { + clearTimeout(prom.timeout); + } prom.resolve(msg); + this.#promises.delete(msg.response_to); + } + + #postMessage(msg: Message, retry: boolean) { + if (!this.#port || !this.#isConnected) { + this.#connect(); + } + if (!this.#port) { + throw new Error("native host is not connected"); + } + try { + this.#port.postMessage(msg); + } catch (exc) { + const err = exc instanceof Error ? exc.message : String(exc); + if (retry && err.includes("disconnected port")) { + this.#isConnected = false; + this.#port = undefined; + this.#connect(); + this.#postMessage(msg, false); + return; + } + throw exc; + } } postMessage(msg: Partial): Promise { msg.id = createRandomString(); const promise = Promise.withResolvers(); try { - this.#promises.set(msg.id, promise); - this.#port?.postMessage(msg); - console.debug(`authentik/bext/native[${msg.id}]: Sending message ${msg.path}`); + const pending = promise as PendingRequest; + pending.timeout = setTimeout(() => { + this.#promises.delete(msg.id as string); + pending.reject(new Error(`native host timed out after ${requestTimeoutMs}ms`)); + }, requestTimeoutMs); + this.#promises.set(msg.id, pending); + this.#postMessage(msg as Message, true); } catch (exc) { - this.#promises.get(msg.id)?.reject(exc); + const pending = this.#promises.get(msg.id); + if (pending?.timeout) { + clearTimeout(pending.timeout); + } + pending?.reject(exc); + this.#promises.delete(msg.id); } return promise.promise; } + #rejectPending(error: Error) { + for (const [id, pending] of this.#promises) { + if (pending.timeout) { + clearTimeout(pending.timeout); + } + pending.reject(error); + this.#promises.delete(id); + } + } + async ping(): Promise { return this.postMessage({ version: "1", diff --git a/pkg/agent_local/tray/items.go b/pkg/agent_local/tray/items.go index 37e003df..9db79cb7 100644 --- a/pkg/agent_local/tray/items.go +++ b/pkg/agent_local/tray/items.go @@ -64,7 +64,7 @@ func (t *Tray) addProfile(name string, profile *config.ConfigV1Profile) { iat.String(), ), "").Disable() i.AddSubMenuItem(fmt.Sprintf( - "Renewing token in %s (%s)", + "Renewing token %s (%s)", timediff.TimeDiff(exp.Time), exp.String(), ), "").Disable() diff --git a/pkg/agent_system/agent_starter/starter.go b/pkg/agent_system/agent_starter/starter.go index 57083fee..c0bf7389 100644 --- a/pkg/agent_system/agent_starter/starter.go +++ b/pkg/agent_system/agent_starter/starter.go @@ -3,6 +3,8 @@ package agentstarter import ( "errors" "os" + "path/filepath" + "runtime" "time" "github.com/avast/retry-go/v4" @@ -10,7 +12,6 @@ import ( "goauthentik.io/platform/pkg/agent_system/component" "goauthentik.io/platform/pkg/agent_system/config" "goauthentik.io/platform/pkg/agent_system/session" - "goauthentik.io/platform/pkg/platform/pstr" "goauthentik.io/platform/pkg/shared/events" "goauthentik.io/platform/vnd/fleet/orbit/pkg/execuser" userpkg "goauthentik.io/platform/vnd/fleet/orbit/pkg/user" @@ -50,7 +51,12 @@ func (as *Server) Stop() error { func (as *Server) RegisterForID(id string, s grpc.ServiceRegistrar) {} func (as *Server) start() { - if _, err := os.Stat(as.agentExec().ForCurrent()); errors.Is(err, os.ErrNotExist) { + agentExec := as.agentExec() + if agentExec == "" { + return + } + if _, err := os.Stat(agentExec); errors.Is(err, os.ErrNotExist) { + as.log.WithField("path", agentExec).Debug("agent executable path not found, skipping") return } for { @@ -75,11 +81,16 @@ func (as *Server) start() { } } -func (as *Server) agentExec() pstr.PlatformString { - return pstr.PlatformString{ - Darwin: new("/Applications/authentik Agent.app"), - Linux: new("/usr/bin/ak-agent"), - Windows: new(`C:\Program Files\Authentik Security Inc\agent\ak-agent.exe`), +func (as *Server) agentExec() string { + switch runtime.GOOS { + case "darwin": + return darwinAgentExec() + case "linux": + return "/usr/bin/ak-agent" + case "windows": + return `C:\Program Files\Authentik Security Inc\agent\ak-agent.exe` + default: + return "" } } @@ -113,10 +124,63 @@ func (as *Server) startSingle() error { // To be able to run the desktop application (mostly to register the icon in the system tray) // we need to run the application as the login user. // Package execuser provides multi-platform support for this. - lastLogs, err := execuser.Run(as.agentExec().ForCurrent(), opts...) + lastLogs, err := execuser.Run(as.agentExec(), opts...) if err != nil { as.log.WithField("logs", lastLogs).WithError(err).Debug("execuser.Run") return err } return nil } + +func darwinAgentExec() string { + if override := os.Getenv("AUTHENTIK_AGENT_APP_PATH"); override != "" { + return override + } + executablePath, err := os.Executable() + if err != nil { + executablePath = "" + } + return firstExistingPath(darwinAgentExecCandidates(executablePath), func(path string) bool { + _, err := os.Stat(path) + return err == nil + }) +} + +func darwinAgentExecCandidates(executablePath string) []string { + candidates := []string{ + "/Applications/authentik Agent.app", + "/Applications/Nix Apps/authentik Agent.app", + } + if executablePath != "" { + candidates = append(candidates, filepath.Clean(filepath.Join(executablePath, "..", "..", ".."))) + } + return uniqueStrings(candidates) +} + +func firstExistingPath(candidates []string, exists func(string) bool) string { + for _, candidate := range candidates { + if candidate != "" && exists(candidate) { + return candidate + } + } + if len(candidates) == 0 { + return "" + } + return candidates[0] +} + +func uniqueStrings(values []string) []string { + seen := map[string]struct{}{} + unique := make([]string, 0, len(values)) + for _, value := range values { + if value == "" { + continue + } + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + unique = append(unique, value) + } + return unique +} diff --git a/pkg/agent_system/agent_starter/starter_test.go b/pkg/agent_system/agent_starter/starter_test.go new file mode 100644 index 00000000..30a17e8d --- /dev/null +++ b/pkg/agent_system/agent_starter/starter_test.go @@ -0,0 +1,56 @@ +package agentstarter + +import ( + "path/filepath" + "testing" +) + +func TestDarwinAgentExecCandidates(t *testing.T) { + executablePath := "/nix/store/test-ak-agent/Applications/authentik Agent.app/Contents/MacOS/ak-sysd" + expectedBundlePath := filepath.Clean(filepath.Join(executablePath, "..", "..", "..")) + + candidates := darwinAgentExecCandidates(executablePath) + + if len(candidates) != 3 { + t.Fatalf("expected 3 candidates, got %d: %#v", len(candidates), candidates) + } + if candidates[0] != "/Applications/authentik Agent.app" { + t.Fatalf("unexpected first candidate: %q", candidates[0]) + } + if candidates[1] != "/Applications/Nix Apps/authentik Agent.app" { + t.Fatalf("unexpected second candidate: %q", candidates[1]) + } + if candidates[2] != expectedBundlePath { + t.Fatalf("unexpected bundle candidate: %q", candidates[2]) + } +} + +func TestFirstExistingPath(t *testing.T) { + candidates := []string{ + "/Applications/authentik Agent.app", + "/Applications/Nix Apps/authentik Agent.app", + } + + selected := firstExistingPath(candidates, func(path string) bool { + return path == "/Applications/Nix Apps/authentik Agent.app" + }) + + if selected != "/Applications/Nix Apps/authentik Agent.app" { + t.Fatalf("unexpected selected path: %q", selected) + } +} + +func TestFirstExistingPathFallback(t *testing.T) { + candidates := []string{ + "/Applications/authentik Agent.app", + "/Applications/Nix Apps/authentik Agent.app", + } + + selected := firstExistingPath(candidates, func(string) bool { + return false + }) + + if selected != "/Applications/authentik Agent.app" { + t.Fatalf("unexpected fallback path: %q", selected) + } +} diff --git a/pkg/agent_system/config/config.go b/pkg/agent_system/config/config.go index 23e904c9..70aca57c 100644 --- a/pkg/agent_system/config/config.go +++ b/pkg/agent_system/config/config.go @@ -15,6 +15,9 @@ import ( var manager *cfgmgr.Manager[*Config] var st *state.State +var keyringGet = keyring.Get +var keyringSet = keyring.Set +var keyringDelete = keyring.Delete func Init(path string, statePath string) error { sst, err := state.Open(statePath, nil) @@ -78,7 +81,7 @@ func (c *Config) Domains() []*DomainConfig { func (c *Config) SaveDomain(dom *DomainConfig) error { path := filepath.Join(c.DomainDir, dom.Domain+".json") - err := keyring.Set(keyring.Service("domain_token"), dom.Domain, keyring.AccessibleAlways, dom.Token) + err := keyringSet(keyring.Service("domain_token"), dom.Domain, keyring.AccessibleAlways, dom.Token) if err != nil { if !errors.Is(err, keyring.ErrUnsupportedPlatform) { c.log.WithError(err).Warning("failed to save domain token in keyring") @@ -98,12 +101,11 @@ func (c *Config) SaveDomain(dom *DomainConfig) error { func (c *Config) DeleteDomain(dom *DomainConfig) error { path := filepath.Join(c.DomainDir, dom.Domain+".json") - err := keyring.Delete(keyring.Service("domain_token"), dom.Domain, keyring.AccessibleAlways) + err := keyringDelete(keyring.Service("domain_token"), dom.Domain, keyring.AccessibleAlways) if err != nil { if !errors.Is(err, keyring.ErrUnsupportedPlatform) { c.log.WithError(err).Warning("failed to delete domain token in keyring") } - dom.FallbackToken = dom.Token } err = os.Remove(path) if err != nil { diff --git a/pkg/agent_system/config/config_test.go b/pkg/agent_system/config/config_test.go new file mode 100644 index 00000000..9a6ca6a1 --- /dev/null +++ b/pkg/agent_system/config/config_test.go @@ -0,0 +1,94 @@ +package config + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "goauthentik.io/platform/pkg/platform/keyring" + "goauthentik.io/platform/pkg/storage/state" +) + +func TestSaveDomainDoesNotPersistFallbackTokenWhenKeyringSucceeds(t *testing.T) { + dir := t.TempDir() + testState, err := state.Open(filepath.Join(dir, "state.db"), nil) + require.NoError(t, err) + st = testState + oldSet := keyringSet + oldGet := keyringGet + t.Cleanup(func() { + require.NoError(t, testState.Close()) + st = nil + keyringSet = oldSet + keyringGet = oldGet + }) + + keyringSet = func(service string, user string, access keyring.Accessibility, password string) error { + return nil + } + keyringGet = func(service string, user string, access keyring.Accessibility) (string, error) { + return "device-token", nil + } + + cfg := &Config{ + DomainDir: dir, + log: (&Config{}).Default().(*Config).log, + } + + dom := cfg.NewDomain() + dom.Domain = "authentik" + dom.AuthentikURL = "http://127.0.0.1:1" + dom.Token = "device-token" + + require.NoError(t, cfg.SaveDomain(dom)) + + raw, err := os.ReadFile(filepath.Join(dir, "authentik.json")) + require.NoError(t, err) + + saved := &DomainConfig{} + require.NoError(t, json.Unmarshal(raw, saved)) + require.Empty(t, saved.FallbackToken) +} + +func TestSaveDomainPersistsFallbackTokenWhenKeyringUnsupported(t *testing.T) { + dir := t.TempDir() + testState, err := state.Open(filepath.Join(dir, "state.db"), nil) + require.NoError(t, err) + st = testState + oldSet := keyringSet + oldGet := keyringGet + t.Cleanup(func() { + require.NoError(t, testState.Close()) + st = nil + keyringSet = oldSet + keyringGet = oldGet + }) + + keyringSet = func(service string, user string, access keyring.Accessibility, password string) error { + return keyring.ErrUnsupportedPlatform + } + keyringGet = func(service string, user string, access keyring.Accessibility) (string, error) { + return "", keyring.ErrUnsupportedPlatform + } + + cfg := &Config{ + DomainDir: dir, + log: (&Config{}).Default().(*Config).log, + } + + dom := cfg.NewDomain() + dom.Domain = "authentik" + dom.AuthentikURL = "http://127.0.0.1:1" + dom.Token = "device-token" + + require.NoError(t, cfg.SaveDomain(dom)) + + raw, err := os.ReadFile(filepath.Join(dir, "authentik.json")) + require.NoError(t, err) + + saved := &DomainConfig{} + require.NoError(t, json.Unmarshal(raw, saved)) + require.Equal(t, "device-token", saved.FallbackToken) +} diff --git a/pkg/agent_system/config/domain.go b/pkg/agent_system/config/domain.go index 5017d21e..1f0dc27f 100644 --- a/pkg/agent_system/config/domain.go +++ b/pkg/agent_system/config/domain.go @@ -231,7 +231,7 @@ func (c *Config) loadDomains() error { c.log.WithError(err).Warning("failed to load domain") continue } - token, err := keyring.Get(keyring.Service("domain_token"), d.Domain, keyring.AccessibleAlways) + token, err := keyringGet(keyring.Service("domain_token"), d.Domain, keyring.AccessibleAlways) if err != nil { if !errors.Is(err, keyring.ErrUnsupportedPlatform) { c.log.WithError(err).Warning("failed to load domain token from keyring") diff --git a/pkg/agent_system/device/signed.go b/pkg/agent_system/device/signed.go index c41f0360..8a950b9d 100644 --- a/pkg/agent_system/device/signed.go +++ b/pkg/agent_system/device/signed.go @@ -6,7 +6,6 @@ import ( "time" "github.com/MicahParks/jwkset" - "github.com/MicahParks/keyfunc/v3" "github.com/golang-jwt/jwt/v5" "github.com/mitchellh/mapstructure" "goauthentik.io/platform/pkg/agent_system/config" @@ -16,28 +15,48 @@ import ( "goauthentik.io/platform/pkg/platform/facts/hardware" ) -func (ds *Server) validateChallenge(ctx context.Context, rawToken string) (*token.Token, *config.DomainConfig, error) { - for _, dom := range config.Manager().Get().Domains() { - var st jwkset.Storage - jw := jwkset.JWKSMarshal{} - err := mapstructure.Decode(dom.Config().JwksChallenge, &jw) - if err != nil { - ds.log.WithField("domain", dom.Domain).WithError(err).Warning("failed to load config") - continue +func parseChallengeToken(rawToken string, rawJWKS map[string]any) (*jwt.Token, error) { + jw := jwkset.JWKSMarshal{} + err := mapstructure.Decode(rawJWKS, &jw) + if err != nil { + return nil, err + } + keys, err := jw.JWKSlice() + if err != nil { + return nil, err + } + headerOnly := &token.AuthentikClaims{} + parser := jwt.NewParser() + unverified, _, err := parser.ParseUnverified(rawToken, headerOnly) + if err != nil { + return nil, err + } + targetKID, _ := unverified.Header["kid"].(string) + var lastErr error + for _, key := range keys { + if targetKID != "" { + marshaled := key.Marshal() + if marshaled.KID != "" && marshaled.KID != targetKID { + continue + } } - sst, err := jw.ToStorage() - if err != nil { - ds.log.WithField("domain", dom.Domain).WithError(err).Warning("failed to parse jwks") - continue + parsed, err := jwt.ParseWithClaims(rawToken, &token.AuthentikClaims{}, func(*jwt.Token) (any, error) { + return key.Key(), nil + }) + if err == nil { + return parsed, nil } - st = sst + lastErr = err + } + if lastErr != nil { + return nil, lastErr + } + return nil, errors.New("no matching jwk found for challenge") +} - k, err := keyfunc.New(keyfunc.Options{Storage: st, Ctx: ctx}) - if err != nil { - ds.log.WithField("domain", dom.Domain).WithError(err).Warning("failed to create keyfunc") - continue - } - t, err := jwt.ParseWithClaims(rawToken, &token.AuthentikClaims{}, k.Keyfunc) +func (ds *Server) validateChallenge(_ context.Context, rawToken string) (*token.Token, *config.DomainConfig, error) { + for _, dom := range config.Manager().Get().Domains() { + t, err := parseChallengeToken(rawToken, dom.Config().JwksChallenge) if err != nil { ds.log.WithField("domain", dom.Domain).WithError(err).Warning("failed to validate token") continue diff --git a/pkg/agent_system/device/signed_test.go b/pkg/agent_system/device/signed_test.go new file mode 100644 index 00000000..93633c91 --- /dev/null +++ b/pkg/agent_system/device/signed_test.go @@ -0,0 +1,47 @@ +package device + +import ( + "crypto/rand" + "crypto/rsa" + "encoding/json" + "testing" + + "github.com/MicahParks/jwkset" + "github.com/golang-jwt/jwt/v5" + "github.com/stretchr/testify/require" +) + +func TestParseChallengeToken(t *testing.T) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + jwk, err := jwkset.NewJWKFromKey(&key.PublicKey, jwkset.JWKOptions{ + Metadata: jwkset.JWKMetadataOptions{ + ALG: jwkset.ALG("RS256"), + KID: "test-kid", + USE: jwkset.USE("sig"), + }, + }) + require.NoError(t, err) + + jwksBytes, err := json.Marshal(jwkset.JWKSMarshal{ + Keys: []jwkset.JWKMarshal{jwk.Marshal()}, + }) + require.NoError(t, err) + + var jwks map[string]any + require.NoError(t, json.Unmarshal(jwksBytes, &jwks)) + + // Rebuild the token with the correct kid header. + signed := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ + "iss": "stage", + "aud": "goauthentik.io/platform/endpoint", + }) + signed.Header["kid"] = "test-kid" + rawToken, err := signed.SignedString(key) + require.NoError(t, err) + + parsed, err := parseChallengeToken(rawToken, jwks) + require.NoError(t, err) + require.NotNil(t, parsed) +} diff --git a/pkg/browser_support/handler.go b/pkg/browser_support/handler.go index 439e8cd8..38f24f1b 100644 --- a/pkg/browser_support/handler.go +++ b/pkg/browser_support/handler.go @@ -31,6 +31,7 @@ func (m message) MessageID() string { type response struct { Data map[string]any `json:"data"` + Error string `json:"error,omitempty"` ResponseTo string `json:"response_to"` } @@ -74,7 +75,6 @@ func (bs *BrowserSupport) setup() { }, nil }) bs.l.Handle("get_token", func(in message) (*response, error) { - bs.log.Debugf("Browser host message: '%+v'\n", in) curr, err := bs.agentClient.GetCurrentToken(bs.ctx, &pb.CurrentTokenRequest{ Header: &pb.RequestHeader{ Profile: in.Profile, @@ -83,7 +83,9 @@ func (bs *BrowserSupport) setup() { }) if err != nil { bs.log.WithError(err).Warning("failed to get current token") - return nil, err + return &response{ + Error: err.Error(), + }, nil } return &response{ Data: map[string]any{ @@ -96,7 +98,9 @@ func (bs *BrowserSupport) setup() { res, err := bs.agentClient.ListProfiles(bs.ctx, &emptypb.Empty{}) if err != nil { bs.log.WithError(err).Warning("failed to list profiles") - return nil, err + return &response{ + Error: err.Error(), + }, nil } return &response{ Data: map[string]any{ @@ -113,7 +117,9 @@ func (bs *BrowserSupport) setup() { }) if err != nil { bs.log.WithError(err).Warning("failed to get endpoint header") - return nil, err + return &response{ + Error: err.Error(), + }, nil } return &response{ Data: map[string]any{ diff --git a/pkg/platform/log/logs_darwin.go b/pkg/platform/log/logs_darwin.go index 77e18aab..dad2eca7 100644 --- a/pkg/platform/log/logs_darwin.go +++ b/pkg/platform/log/logs_darwin.go @@ -7,7 +7,6 @@ import ( "sync" "github.com/aletheia7/ul" - "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" ) @@ -54,7 +53,7 @@ func (dlh *darwinLogHook) Release() { } } -func (dlh *darwinLogHook) Fire(e *logrus.Entry) error { +func (dlh *darwinLogHook) Fire(e *log.Entry) error { logger := dlh.def // Attempt to get logger instance for specified `logger` field diff --git a/pkg/platform/log/logs_darwin_test.go b/pkg/platform/log/logs_darwin_test.go index 988149f5..2a895c23 100644 --- a/pkg/platform/log/logs_darwin_test.go +++ b/pkg/platform/log/logs_darwin_test.go @@ -9,7 +9,9 @@ import ( ) func TestDarwin(t *testing.T) { - MustSetup("test") + if err := MustSetup("test"); err != nil { + t.Fatal(err) + } logrus.SetLevel(logrus.DebugLevel) logrus.WithField("logger", "foo").Trace("foob 1") logrus.WithField("logger", "foo.bar").Debug("foob 2")