Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,32 @@

let allowListRegExp: RegExp

const handler: PlasmoMessaging.MessageHandler = async (req, res) => {

Check warning on line 28 in products/userale/packages/flagon-userale-ext/src/background/messages/config_change.ts

View workflow job for this annotation

GitHub Actions / pre-build (22.x)

'res' is defined but never used

Check warning on line 28 in products/userale/packages/flagon-userale-ext/src/background/messages/config_change.ts

View workflow job for this annotation

GitHub Actions / pre-build (22.x)

'res' is defined but never used
setOptions(req.body)
}

export function setOptions(options: StoredOptions) {
userale.options({ url: options.loggingUrl })
allowListRegExp = new RegExp(options.allowList)

switch (options.authMode) {
case "oauth":
userale.options({
authHeader: options.accessToken
? `Bearer ${options.accessToken}`
: null,
apiKey: null
})
break
case "apikey":
userale.options({
authHeader: null,
apiKey: options.apiKey || null
})
break
default:
userale.options({ authHeader: null, apiKey: null })
}
}

export function getAllowListRegExp() {
Expand Down
138 changes: 115 additions & 23 deletions products/userale/packages/flagon-userale-ext/src/options/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,45 @@
*/

import pkceChallenge from "pkce-challenge"
import { useState } from "react"
import { useEffect, useState } from "react"
import browser from "webextension-polyfill"

import { setStoredOptions } from "~/utils/storage"
import { getStoredOptions, setStoredOptions } from "~/utils/storage"
import type { StoredOptions } from "~/utils/storage"

function Auth() {
const [authMode, setAuthMode] = useState<StoredOptions["authMode"]>("none")
const [apiKey, setApiKey] = useState("")
const [issuerUrl, setIssuerUrl] = useState("")
const [clientId, setClientId] = useState("")
const [message, setMessage] = useState("")

useEffect(() => {
getStoredOptions().then((opts) => {
setAuthMode(opts.authMode)
setApiKey(opts.apiKey)
})
}, [])

const handleAuthModeChange = async (mode: StoredOptions["authMode"]) => {
setAuthMode(mode)
setMessage("")
if (mode === "none") {
await setStoredOptions({ authMode: "none", accessToken: "", apiKey: "" })
setApiKey("")
setMessage("Auth disabled.")
}
}

const handleSaveApiKey = async () => {
await setStoredOptions({
authMode: "apikey",
apiKey,
accessToken: ""
})
setMessage("API key saved.")
}

const handleLogin = async () => {
try {
// Generate the PKCE challenge pair (code_verifier and code_challenge)
Expand Down Expand Up @@ -74,7 +103,11 @@ function Auth() {
)

const tokens = await tokenRes.json()
await setStoredOptions({ accessToken: tokens.access_token })
await setStoredOptions({
authMode: "oauth",
accessToken: tokens.access_token,
apiKey: ""
})

setMessage("Login successful!")
} catch (err) {
Expand All @@ -85,32 +118,91 @@ function Auth() {

return (
<div>
<h2>OAuth Login</h2>
<form onSubmit={handleLogin}>
<div>
<label>Issuer URL:</label>
<h2>Authentication</h2>
<div>
<label>
<input
type="url"
value={issuerUrl}
onChange={(e) => setIssuerUrl(e.target.value)}
placeholder="https://issuer.com/realms/myrealm"
required
type="radio"
name="authMode"
value="none"
checked={authMode === "none"}
onChange={() => handleAuthModeChange("none")}
/>
</div>
<div>
<label>Client ID:</label>
None
</label>
<label style={{ marginLeft: 16 }}>
<input
type="radio"
name="authMode"
value="oauth"
checked={authMode === "oauth"}
onChange={() => handleAuthModeChange("oauth")}
/>
OAuth
</label>
<label style={{ marginLeft: 16 }}>
<input
type="text"
value={clientId}
onChange={(e) => setClientId(e.target.value)}
placeholder="your-client-id"
required
type="radio"
name="authMode"
value="apikey"
checked={authMode === "apikey"}
onChange={() => handleAuthModeChange("apikey")}
/>
API Key
</label>
</div>

{authMode === "oauth" && (
<div>
<h3>OAuth Login</h3>
<form onSubmit={handleLogin}>
<div>
<label>Issuer URL:</label>
<input
type="url"
value={issuerUrl}
onChange={(e) => setIssuerUrl(e.target.value)}
placeholder="https://issuer.com/realms/myrealm"
required
/>
</div>
<div>
<label>Client ID:</label>
<input
type="text"
value={clientId}
onChange={(e) => setClientId(e.target.value)}
placeholder="your-client-id"
required
/>
</div>
<div style={{ textAlign: "right" }}>
<button type="submit">Log In</button>
</div>
</form>
</div>
<div style={{ textAlign: "right" }}>
<button type="submit">Log In</button>
)}

{authMode === "apikey" && (
<div>
<h3>API Key</h3>
<div>
<label>API Key:</label>
<input
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="Enter your API key"
/>
</div>
<div style={{ textAlign: "right" }}>
<button type="button" onClick={handleSaveApiKey}>
Save
</button>
</div>
</div>
</form>
)}

{message && <p>{message}</p>}
</div>
)
Expand Down
12 changes: 10 additions & 2 deletions products/userale/packages/flagon-userale-ext/src/utils/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { sendToBackground } from "@plasmohq/messaging"

export const STORAGE_KEYS = {
accessToken: "accessToken",
apiKey: "apiKey",
authMode: "authMode",
allowList: "allowList",
loggingUrl: "loggingUrl"
} as const
Expand All @@ -31,12 +33,16 @@ export type StorageKeys = keyof typeof STORAGE_KEYS

export type StoredOptions = {
accessToken: string
apiKey: string
authMode: "oauth" | "apikey" | "none"
allowList: string
loggingUrl: string
}

const DEFAULT_OPTIONS: StoredOptions = {
accessToken: "",
apiKey: "",
authMode: "none",
allowList: "https://flagon.apache.org/",
loggingUrl: "http://localhost:8000"
}
Expand All @@ -48,6 +54,8 @@ export async function getStoredOptions(): Promise<StoredOptions> {

return {
accessToken: stored.accessToken ?? DEFAULT_OPTIONS.accessToken,
apiKey: stored.apiKey ?? DEFAULT_OPTIONS.apiKey,
authMode: stored.authMode ?? DEFAULT_OPTIONS.authMode,
allowList: stored.allowList ?? DEFAULT_OPTIONS.allowList,
loggingUrl: stored.loggingUrl ?? DEFAULT_OPTIONS.loggingUrl
}
Expand All @@ -57,8 +65,8 @@ export async function getStoredOptions(): Promise<StoredOptions> {
export async function setStoredOptions(values: Partial<StoredOptions>) {
// Validate the new options
try {
new RegExp(values.allowList)
new URL(values.loggingUrl)
if (values.allowList !== undefined) new RegExp(values.allowList)
if (values.loggingUrl !== undefined) new URL(values.loggingUrl)
} catch (error) {
return error
}
Expand Down
1 change: 1 addition & 0 deletions products/userale/packages/flagon-userale/src/configure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class Configuration {
private static instance: Configuration | null = null;

// Public properties corresponding to fields in the Config interface
public apiKey: Settings.ApiKey = null;
public autostart: boolean = false;
public authHeader: Settings.AuthHeader = null;
public browserSessionId: Settings.SessionId = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export function getInitialSettings(): Settings.Config {
self instanceof WorkerGlobalScope
) {
const settings: Settings.Config = {
apiKey: null,
authHeader: null,
autostart: true,
browserSessionId: null,
Expand Down Expand Up @@ -81,6 +82,7 @@ export function getInitialSettings(): Settings.Config {
};
const headers = get("data-headers");
const settings: Settings.Config = {
apiKey: get("data-api-key") || null,
authHeader: get("data-auth") || null,
autostart: get("data-autostart") === "false" ? false : true,
browserSessionId: null,
Expand Down
8 changes: 8 additions & 0 deletions products/userale/packages/flagon-userale/src/sendLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ export function sendOnClose(
headers.set("Authorization", config.authHeader.toString());
}

if (config.apiKey) {
headers.set("x-api-key", config.apiKey);
}

fetch(config.url, {
keepalive: true,
method: "POST",
Expand Down Expand Up @@ -140,6 +144,10 @@ export async function sendLogs(
headers.set("Authorization", authHeaderValue);
}

if (config.apiKey) {
headers.set("x-api-key", config.apiKey);
}

// Update custom headers last to allow them to over-write the defaults. This assumes
// the user knows what they are doing and may want to over-write the defaults.
updateCustomHeaders(config);
Expand Down
17 changes: 5 additions & 12 deletions products/userale/packages/flagon-userale/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export declare namespace Settings {
type UserFromParams = string | null;
type ToolName = string | null;
type AuthHeader = CallableFunction | string | null;
type ApiKey = string | null;
type CustomIndex = string | null;
type HeaderObject = { [key: string]: string };
type Headers = HeaderObject | null;
Expand All @@ -35,6 +36,7 @@ export declare namespace Settings {
| UserFromParams
| ToolName
| AuthHeader
| ApiKey
| CustomIndex
| Headers;

Expand All @@ -46,6 +48,7 @@ export declare namespace Settings {

export interface Config extends DefaultConfig {
autostart: boolean;
apiKey: ApiKey;
authHeader: AuthHeader;
browserSessionId: SessionId;
custIndex: CustomIndex;
Expand Down Expand Up @@ -108,22 +111,12 @@ export declare namespace Events {
| "drop"
| "keydown";
export type IntervalEvents =
| "click"
| "focus"
| "blur"
| "input"
| "change"
| "mouseover"
| "submit";
"click" | "focus" | "blur" | "input" | "change" | "mouseover" | "submit";
export type WindowEvents = "load" | "blur" | "focus";
export type BufferedEvents = "wheel" | "scroll" | "resize";
export type RefreshEvents = "submit";
export type AllowedEvents =
| RawEvents
| IntervalEvents
| WindowEvents
| BufferedEvents
| RefreshEvents;
RawEvents | IntervalEvents | WindowEvents | BufferedEvents | RefreshEvents;

export type EventDetailsMap<T extends string> = Partial<{
[key in T]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ describe("configure", () => {
done();
});

it("allows apiKey to be set via config.update()", (done) => {
config.update({ apiKey: "test-api-key-123" });
expect(config.apiKey).toBe("test-api-key-123");

config.update({ apiKey: null });
expect(config.apiKey).toBeNull();
done();
});

describe("getUserIdFromParams", () => {
it("fetches userId from URL params", (done) => {
Object.defineProperty(global.window, "location", {
Expand Down
Loading
Loading