Skip to content

Commit fcee9f4

Browse files
CopilotMathiasVDACopilot
authored
feat: add endpoint-based HTTP Basic Authentication support with unified SPARQL Endpoints management (#55)
* Initial plan * feat: add basic authentication support - core implementation - Add BasicAuthConfig type to RequestConfig interface - Add basicAuth field to request configuration with username/password - Implement Authorization header generation in sparql.ts - Add Authentication tab in Settings modal with username/password inputs - Add security notice about localStorage and HTTPS usage - Add proper theme-aware styling for auth inputs - Build successfully compiles with new types Co-authored-by: MathiasVDA <15101339+MathiasVDA@users.noreply.github.com> * feat: add documentation and tests for basic authentication - Add comprehensive user and developer documentation - Create unit tests for base64 encoding and auth header format - Document security considerations and best practices - Add code examples for various authentication scenarios - Include troubleshooting guide Co-authored-by: MathiasVDA <15101339+MathiasVDA@users.noreply.github.com> * fix: address code review feedback for basic auth - Add error handling for basicAuth function calls - Ensure authentication is cleared when credentials are incomplete - Improve consistency of authentication state management Co-authored-by: MathiasVDA <15101339+MathiasVDA@users.noreply.github.com> * docs: integrate basic auth into user and developer guides - Move basic auth documentation from standalone file into existing guides - Add authentication section to user guide after Settings Modal - Add authentication section to developer guide in Request Configuration - Remove standalone docs/basic-authentication.md file - Keep user and developer documentation together in their respective guides Co-authored-by: MathiasVDA <15101339+MathiasVDA@users.noreply.github.com> * feat: change layout of settings to vertical sections * refactor: change authentication from tab-based to endpoint-based (WIP) - Add EndpointConfig interface for endpoint-based storage with authentication - Update PersistentConfig to store endpoint configurations instead of tab-based auth - Add methods to manage endpoint configs (get, set, add/update, delete) - Modify Tab to inject authentication from endpoint config into request config - Add auto-tracking of accessed endpoints - Rename "Endpoint Buttons" tab to "SPARQL Endpoints" in settings modal - Remove standalone "Authentication" tab from settings modal - Preparing for comprehensive SPARQL Endpoints UI (next commit) Co-authored-by: MathiasVDA <15101339+MathiasVDA@users.noreply.github.com> * feat: implement SPARQL Endpoints UI with authentication per endpoint - Create comprehensive SPARQL Endpoints settings tab - Replace separate Authentication and Endpoint Buttons tabs - Add endpoints table with columns: Endpoint, Label, Button, Authentication, Actions - Implement authentication modal per endpoint (designed for future auth types) - Add auto-tracking of accessed endpoints - Update endpoint buttons to use new endpointConfigs with showAsButton flag - Add complete CSS styling for table and modal with theme support - Maintain backwards compatibility with legacy customEndpointButtons - Build successfully compiles Co-authored-by: MathiasVDA <15101339+MathiasVDA@users.noreply.github.com> * docs: update user and developer guides for endpoint-based authentication - Update user guide to reflect SPARQL Endpoints tab instead of Authentication tab - Document endpoint-based storage model - Add instructions for managing endpoints and configuring authentication per endpoint - Update developer guide with endpoint configuration API examples - Remove references to per-tab authentication - Emphasize that authentication is now shared across tabs using the same endpoint Co-authored-by: MathiasVDA <15101339+MathiasVDA@users.noreply.github.com> * fix: modal settings window layout issues * feat: add manual endpoint addition in SPARQL Endpoints settings - Add "Add New Endpoint" form with URL input and "+ Add Endpoint" button - Allow users to manually add endpoints instead of only auto-tracking - Validate URL format before adding - Check for duplicate endpoints - Show improved empty message: "No endpoints yet. Add one below or access an endpoint to have it automatically tracked." - Add themed CSS styling for form (background, borders, button) - Full theme support with CSS custom properties for light/dark modes Co-authored-by: MathiasVDA <15101339+MathiasVDA@users.noreply.github.com> * Update packages/yasgui/src/TabSettingsModal.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/yasqe/src/sparql.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/yasqe/src/sparql.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: address code review feedback - Add check for existing Authorization header before adding basic auth - Only inject endpoint-based auth if basicAuth not already set programmatically - Properly delete authentication field when set to undefined in updates - Add aria-label to "Show as Button" checkbox for accessibility - Update documentation to clarify endpoint-based (not tab-based) authentication - Add security warnings about localStorage credential storage - Recommend secure alternatives (session tokens, OAuth) in examples Co-authored-by: MathiasVDA <15101339+MathiasVDA@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: MathiasVDA <15101339+MathiasVDA@users.noreply.github.com> Co-authored-by: Mathias Vanden Auweele <mathias@matdata.eu> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent bb83d54 commit fcee9f4

File tree

12 files changed

+1300
-169
lines changed

12 files changed

+1300
-169
lines changed

docs/developer-guide.md

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,14 @@ interface RequestConfig {
904904

905905
// CORS proxy
906906
corsProxy?: string;
907+
908+
// Basic Authentication
909+
basicAuth?: BasicAuthConfig | ((yasqe: Yasqe) => BasicAuthConfig | undefined);
910+
}
911+
912+
interface BasicAuthConfig {
913+
username: string;
914+
password: string;
907915
}
908916
```
909917

@@ -937,6 +945,202 @@ const config = {
937945
};
938946
```
939947

948+
### Basic Authentication
949+
950+
YASGUI supports HTTP Basic Authentication for SPARQL endpoints that require username and password credentials. **Authentication is stored per-endpoint**, meaning all tabs using the same endpoint share the same credentials.
951+
952+
#### Programmatic Configuration
953+
954+
Configure basic authentication programmatically when initializing YASGUI. Note that this sets the initial credentials, but users can also configure them via the UI.
955+
956+
```javascript
957+
const yasgui = new Yasgui(document.getElementById("yasgui"), {
958+
requestConfig: {
959+
endpoint: "https://example.com/sparql",
960+
basicAuth: {
961+
username: "myuser",
962+
password: "mypassword"
963+
}
964+
}
965+
});
966+
```
967+
968+
**Important:** When using programmatic configuration, the credentials will be used for that initial request, but YASGUI will store them in the endpoint-based configuration. Any subsequent tab that uses the same endpoint will automatically use these credentials.
969+
970+
#### Managing Endpoint Configurations
971+
972+
Use the PersistentConfig API to manage endpoint configurations programmatically:
973+
974+
```javascript
975+
// Add or update an endpoint with authentication
976+
yasgui.persistentConfig.addOrUpdateEndpoint("https://example.com/sparql", {
977+
label: "My Secure Endpoint",
978+
showAsButton: true,
979+
authentication: {
980+
type: 'basic',
981+
username: "myuser",
982+
password: "mypassword"
983+
}
984+
});
985+
986+
// Get endpoint configuration
987+
const config = yasgui.persistentConfig.getEndpointConfig("https://example.com/sparql");
988+
989+
// Remove authentication from an endpoint
990+
yasgui.persistentConfig.addOrUpdateEndpoint("https://example.com/sparql", {
991+
authentication: undefined
992+
});
993+
994+
// Delete an endpoint completely
995+
yasgui.persistentConfig.deleteEndpointConfig("https://example.com/sparql");
996+
```
997+
998+
#### Dynamic Authentication
999+
1000+
Use a function to dynamically provide credentials:
1001+
1002+
```javascript
1003+
const yasgui = new Yasgui(document.getElementById("yasgui"), {
1004+
requestConfig: {
1005+
endpoint: "https://example.com/sparql",
1006+
basicAuth: (yasqe) => {
1007+
// Return credentials dynamically
1008+
return {
1009+
username: getCurrentUsername(),
1010+
password: getCurrentPassword()
1011+
};
1012+
}
1013+
}
1014+
});
1015+
```
1016+
1017+
#### Disabling Authentication
1018+
1019+
To disable authentication:
1020+
1021+
```javascript
1022+
tab.setRequestConfig({
1023+
basicAuth: undefined
1024+
});
1025+
```
1026+
1027+
#### TypeScript Support
1028+
1029+
```typescript
1030+
import { BasicAuthConfig } from "@matdata/yasqe";
1031+
1032+
const authConfig: BasicAuthConfig = {
1033+
username: "myuser",
1034+
password: "mypassword"
1035+
};
1036+
1037+
const yasgui = new Yasgui(element, {
1038+
requestConfig: {
1039+
endpoint: "https://secure-endpoint.com/sparql",
1040+
basicAuth: authConfig
1041+
}
1042+
});
1043+
```
1044+
1045+
#### Examples
1046+
1047+
**Example 1: Simple Authentication**
1048+
1049+
```javascript
1050+
const yasgui = new Yasgui(document.getElementById("yasgui"), {
1051+
requestConfig: {
1052+
endpoint: "https://secure-endpoint.example.com/sparql",
1053+
basicAuth: {
1054+
username: "user",
1055+
password: "pass"
1056+
}
1057+
}
1058+
});
1059+
```
1060+
1061+
**Example 2: Multiple Endpoints with Different Credentials**
1062+
1063+
Authentication is stored **per-endpoint**, not per-tab. All tabs using the same endpoint will share the same credentials.
1064+
1065+
```javascript
1066+
const yasgui = new Yasgui(document.getElementById("yasgui"));
1067+
1068+
// Configure authentication for first endpoint
1069+
yasgui.persistentConfig.addOrUpdateEndpoint("https://dbpedia.org/sparql", {
1070+
// No authentication needed for public endpoint
1071+
});
1072+
1073+
// Configure authentication for second endpoint
1074+
yasgui.persistentConfig.addOrUpdateEndpoint("https://private.example.com/sparql", {
1075+
authentication: {
1076+
type: "basic",
1077+
username: "admin",
1078+
password: "secret"
1079+
}
1080+
});
1081+
1082+
// Create tabs - they will automatically use endpoint-based auth
1083+
const tab1 = yasgui.addTab();
1084+
tab1.setRequestConfig({ endpoint: "https://dbpedia.org/sparql" });
1085+
1086+
const tab2 = yasgui.addTab();
1087+
tab2.setRequestConfig({ endpoint: "https://private.example.com/sparql" });
1088+
1089+
// Both tabs pointing to the same endpoint will share credentials
1090+
const tab3 = yasgui.addTab();
1091+
tab3.setRequestConfig({ endpoint: "https://private.example.com/sparql" }); // Uses same auth as tab2
1092+
```
1093+
1094+
**Example 3: Prompt User for Credentials**
1095+
1096+
⚠️ **Security Warning**: Storing credentials in localStorage exposes them to any script running on the same origin (e.g., XSS attacks or malicious third-party scripts). For production use:
1097+
- Use session-based authentication with short-lived tokens
1098+
- Consider OAuth 2.0 or other secure authentication flows
1099+
- Avoid storing reusable passwords in browser storage
1100+
- Only use HTTPS endpoints
1101+
1102+
```javascript
1103+
const yasgui = new Yasgui(document.getElementById("yasgui"), {
1104+
requestConfig: {
1105+
endpoint: "https://secure-endpoint.example.com/sparql",
1106+
basicAuth: (yasqe) => {
1107+
// WARNING: This example stores credentials in localStorage for demonstration only.
1108+
// In production, use more secure alternatives (session tokens, OAuth, etc.)
1109+
1110+
const username = prompt("Enter username:");
1111+
const password = prompt("Enter password:");
1112+
1113+
if (username && password) {
1114+
return { username, password };
1115+
}
1116+
1117+
return undefined;
1118+
}
1119+
}
1120+
});
1121+
```
1122+
1123+
**Recommended Approach**: Use endpoint-based authentication via the UI or configure it once programmatically:
1124+
1125+
```javascript
1126+
// Configure authentication securely
1127+
yasgui.persistentConfig.addOrUpdateEndpoint("https://secure-endpoint.example.com/sparql", {
1128+
authentication: {
1129+
type: "basic",
1130+
username: "user",
1131+
password: "pass" // Consider using environment variables or secure credential management
1132+
}
1133+
});
1134+
```
1135+
1136+
#### Security Best Practices
1137+
1138+
1. **Use HTTPS Only**: Never use basic authentication with HTTP endpoints
1139+
2. **Secure Storage**: Consider implementing additional encryption for stored credentials
1140+
3. **Token-Based Auth**: For production applications, consider using token-based authentication instead of basic auth
1141+
4. **Clear on Logout**: Implement a logout mechanism that clears stored credentials
1142+
5. **Environment Variables**: Store credentials in environment variables for server-side applications
1143+
9401144
### Endpoint Buttons Configuration
9411145

9421146
The endpoint quick switch buttons feature allows you to configure a list of predefined SPARQL endpoints that users can quickly switch between with a single click.

docs/user-guide.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,8 +511,71 @@ Access comprehensive configuration options through the Settings modal.
511511
- Select formatter (sparql-formatter or legacy)
512512
- Enable/disable auto-format on query execution
513513

514+
**SPARQL Endpoints Tab:**
515+
- Manage all your SPARQL endpoints in one place
516+
- Configure authentication per endpoint
517+
- Add labels and create quick-switch buttons
518+
- Automatically tracks accessed endpoints
519+
514520
All settings are saved automatically to local storage.
515521

522+
### SPARQL Endpoints Management
523+
524+
YASGUI automatically tracks all SPARQL endpoints you access and lets you manage them from a single location.
525+
526+
**Accessing the Endpoints Manager:**
527+
528+
1. **Open Settings**: Click the settings icon (⚙️) in the control bar
529+
2. **Navigate to SPARQL Endpoints**: Click on the "SPARQL Endpoints" tab in the settings modal
530+
531+
**Managing Endpoints:**
532+
533+
The endpoints table shows:
534+
- **Endpoint**: The URL of the SPARQL endpoint
535+
- **Label**: Optional friendly name for the endpoint
536+
- **Button**: Checkbox to show endpoint as a quick-switch button (requires label)
537+
- **Authentication**: Configure HTTP Basic Authentication
538+
- **Actions**: Delete endpoint from the list
539+
540+
**Adding Quick-Switch Buttons:**
541+
542+
1. Find your endpoint in the list
543+
2. Enter a label (e.g., "DBpedia", "Wikidata")
544+
3. Check the "Button" checkbox
545+
4. The endpoint will now appear as a button in the control bar
546+
547+
**Configuring Authentication:**
548+
549+
YASGUI supports HTTP Basic Authentication for endpoints that require username and password credentials.
550+
551+
1. **Find your endpoint** in the SPARQL Endpoints table
552+
2. **Click "Configure"** in the Authentication column
553+
3. **Select authentication type** (currently HTTP Basic Authentication)
554+
4. **Enter credentials**:
555+
- Username: Your endpoint username
556+
- Password: Your endpoint password
557+
5. **Click "Save"** to apply
558+
559+
**Security Considerations:**
560+
561+
⚠️ **Important Security Notes:**
562+
563+
- **Credentials are stored in browser localStorage**: Your username and password are stored locally in your browser
564+
- **Only use with HTTPS endpoints**: Never send credentials to HTTP endpoints as they will be transmitted in plain text
565+
- **Be cautious on shared computers**: Clear your browser data when using YASGUI on shared or public computers
566+
567+
**How Authentication Works:**
568+
569+
Authentication is stored per-endpoint, which means:
570+
- All tabs using the same endpoint share the same credentials
571+
- You only need to configure authentication once per endpoint
572+
- Credentials persist across browser sessions (stored in localStorage)
573+
574+
When authentication is configured:
575+
1. YASGUI encodes your credentials using Base64 encoding
576+
2. Adds an `Authorization` header with the format: `Basic <encoded-credentials>`
577+
3. Sends this header with every SPARQL query request to that endpoint
578+
516579
### Query History and Persistence
517580

518581
YASGUI automatically saves your work locally.

packages/yasgui/src/ConfigExportImport.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ export function parseFromTurtle(turtle: string): Partial<PersistedJson> {
304304
headers: {},
305305
withCredentials: false,
306306
adjustQueryBeforeRequest: false,
307+
basicAuth: undefined,
307308
},
308309
yasr: {
309310
settings: {},

packages/yasgui/src/PersistentConfig.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Storage as YStorage } from "@matdata/yasgui-utils";
2-
import Yasgui, { EndpointButton } from "./";
2+
import Yasgui, { EndpointButton, EndpointConfig } from "./";
33
import * as Tab from "./Tab";
44
export var storageNamespace = "triply";
55
export interface PersistedJson {
@@ -10,7 +10,8 @@ export interface PersistedJson {
1010
lastClosedTab: { index: number; tab: Tab.PersistedJson } | undefined;
1111
prefixes?: string;
1212
autoCaptureEnabled?: boolean;
13-
customEndpointButtons?: EndpointButton[];
13+
customEndpointButtons?: EndpointButton[]; // Legacy, kept for backwards compatibility
14+
endpointConfigs?: EndpointConfig[]; // New endpoint-based storage with auth
1415
theme?: "light" | "dark";
1516
orientation?: "vertical" | "horizontal";
1617
}
@@ -24,6 +25,7 @@ function getDefaults(): PersistedJson {
2425
prefixes: "",
2526
autoCaptureEnabled: true,
2627
customEndpointButtons: [],
28+
endpointConfigs: [],
2729
};
2830
}
2931

@@ -163,6 +165,46 @@ export default class PersistentConfig {
163165
this.persistedJson.customEndpointButtons = buttons;
164166
this.toStorage();
165167
}
168+
169+
// New endpoint configuration methods
170+
public getEndpointConfigs(): EndpointConfig[] {
171+
return this.persistedJson.endpointConfigs || [];
172+
}
173+
174+
public setEndpointConfigs(configs: EndpointConfig[]) {
175+
this.persistedJson.endpointConfigs = configs;
176+
this.toStorage();
177+
}
178+
179+
public addOrUpdateEndpoint(endpoint: string, updates: Partial<Omit<EndpointConfig, "endpoint">>) {
180+
const configs = this.getEndpointConfigs();
181+
const existingIndex = configs.findIndex((c) => c.endpoint === endpoint);
182+
183+
if (existingIndex >= 0) {
184+
// Update existing endpoint
185+
const merged = { ...configs[existingIndex], ...updates };
186+
if ("authentication" in updates && updates.authentication === undefined) {
187+
delete merged.authentication;
188+
}
189+
configs[existingIndex] = merged;
190+
} else {
191+
// Add new endpoint
192+
configs.push({ endpoint, ...updates });
193+
}
194+
195+
this.setEndpointConfigs(configs);
196+
}
197+
198+
public getEndpointConfig(endpoint: string): EndpointConfig | undefined {
199+
const configs = this.getEndpointConfigs();
200+
return configs.find((c) => c.endpoint === endpoint);
201+
}
202+
203+
public deleteEndpointConfig(endpoint: string) {
204+
const configs = this.getEndpointConfigs();
205+
const filtered = configs.filter((c) => c.endpoint !== endpoint);
206+
this.setEndpointConfigs(filtered);
207+
}
166208
public static clear() {
167209
const storage = new YStorage(storageNamespace);
168210
storage.removeNamespace();

0 commit comments

Comments
 (0)