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
12 changes: 9 additions & 3 deletions platforms/esigner-api/src/services/NotificationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ export class NotificationService {
const inviterText = inviterName ? ` from ${inviterName}` : '';
const containerName = file.displayName || file.name;
const descriptionText = file.description ? `\nDescription: ${file.description}` : '';
const esignerUrl = process.env.PUBLIC_ESIGNER_BASE_URL || 'http://localhost:3004';
const fileLink = `${esignerUrl}/files/${file.id}`;
Comment on lines +288 to +289
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Search for PUBLIC_ESIGNER_BASE_URL configuration across the codebase
rg -i "PUBLIC_ESIGNER_BASE_URL" --type-list | head -20

Repository: MetaState-Prototype-Project/prototype

Length of output: 470


🏁 Script executed:

# Look for environment configuration files for esigner-api
fd -type f -name ".env*" platforms/esigner-api/ 2>/dev/null | head -20

Repository: MetaState-Prototype-Project/prototype

Length of output: 63


🏁 Script executed:

# Search for how file.id is defined and used
rg "file\.id" platforms/esigner-api/src/services/NotificationService.ts -B 3 -A 3

Repository: MetaState-Prototype-Project/prototype

Length of output: 1137


🏁 Script executed:

# Check if there are URL encoding utilities being used elsewhere
rg "encodeURI|encodeURIComponent" platforms/esigner-api/src/ -l

Repository: MetaState-Prototype-Project/prototype

Length of output: 135


🏁 Script executed:

# Verify the complete context of the generateInvitationMessage function
sed -n '276,302p' platforms/esigner-api/src/services/NotificationService.ts

Repository: MetaState-Prototype-Project/prototype

Length of output: 1104


🏁 Script executed:

# Search for PUBLIC_ESIGNER_BASE_URL across entire repo
rg "PUBLIC_ESIGNER_BASE_URL" -n

Repository: MetaState-Prototype-Project/prototype

Length of output: 1889


🏁 Script executed:

# Check where environment variables are configured for esigner-api
rg -type yaml -type json "PUBLIC_ESIGNER_BASE_URL" platforms/esigner-api/ 2>/dev/null || rg "PUBLIC_ESIGNER_BASE_URL" platforms/ infrastructure/

Repository: MetaState-Prototype-Project/prototype

Length of output: 1846


🏁 Script executed:

# Find File type definition to understand file.id
rg -A 5 "interface File\|class File\|type File" platforms/esigner-api/src/ | head -40

Repository: MetaState-Prototype-Project/prototype

Length of output: 63


🏁 Script executed:

# Search for environment configuration patterns in the project
fd -type f \( -name "*.example" -o -name "*.config.*" -o -name ".env.template" \) | grep -E "(esigner|env)"

Repository: MetaState-Prototype-Project/prototype

Length of output: 251


🏁 Script executed:

# Check if there's a pattern of removing fallbacks similar to the learning
rg "process\.env\." platforms/esigner-api/src/services/ | grep -c "\|\|"

Repository: MetaState-Prototype-Project/prototype

Length of output: 82


🏁 Script executed:

# Find File type definition
rg "interface File|type File" platforms/esigner-api/src/ -A 10 | head -50

Repository: MetaState-Prototype-Project/prototype

Length of output: 63


🏁 Script executed:

# Check if there's a broader refactoring or issue about removing fallbacks
rg "PUBLIC_" platforms/esigner-api/src/ | grep -v "||" | head -20

Repository: MetaState-Prototype-Project/prototype

Length of output: 920


🏁 Script executed:

# Look for .env configuration to see if PUBLIC_ESIGNER_BASE_URL is documented
find . -name ".env*" -o -name "*.example" | head -20

Repository: MetaState-Prototype-Project/prototype

Length of output: 205


🏁 Script executed:

# Check the registry to see how PUBLIC_ESIGNER_BASE_URL is passed
sed -n '170,180p' platforms/registry/src/index.ts

Repository: MetaState-Prototype-Project/prototype

Length of output: 380


🏁 Script executed:

# Check the root .env.example
cat .env.example | grep -i "esigner\|platform" | head -20

Repository: MetaState-Prototype-Project/prototype

Length of output: 213


🏁 Script executed:

# Check if there are any comments or documentation about PUBLIC_ESIGNER_BASE_URL
rg -B 2 -A 2 "PUBLIC_ESIGNER_BASE_URL" platforms/registry/src/index.ts

Repository: MetaState-Prototype-Project/prototype

Length of output: 270


🏁 Script executed:

# Verify if file.id could contain special characters by checking File usage
rg "file\.id" platforms/esigner-api/src/ -B 2 -A 2 | head -40

Repository: MetaState-Prototype-Project/prototype

Length of output: 3673


🏁 Script executed:

# Check SignatureService to see how it handles missing PUBLIC_ESIGNER_BASE_URL
sed -n '70,85p' platforms/esigner-api/src/services/SignatureService.ts

Repository: MetaState-Prototype-Project/prototype

Length of output: 764


Remove localhost fallback from PUBLIC_ESIGNER_BASE_URL—environment variable must be explicitly configured in production.

The localhost default will not work in production and conflicts with the architectural pattern of removing static fallbacks from PUBLIC_* environment variables. Additionally, file.id should be URL-encoded to safely handle special characters.

This pattern appears in:

  • NotificationService.ts lines 288, 319, 347
  • SignatureService.ts line 75
Recommended fix
- const esignerUrl = process.env.PUBLIC_ESIGNER_BASE_URL || 'http://localhost:3004';
- const fileLink = `${esignerUrl}/files/${file.id}`;
+ const esignerUrl = process.env.PUBLIC_ESIGNER_BASE_URL;
+ if (!esignerUrl) {
+   throw new Error("PUBLIC_ESIGNER_BASE_URL is not configured");
+ }
+ const fileLink = `${esignerUrl}/files/${encodeURIComponent(file.id)}`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const esignerUrl = process.env.PUBLIC_ESIGNER_BASE_URL || 'http://localhost:3004';
const fileLink = `${esignerUrl}/files/${file.id}`;
const esignerUrl = process.env.PUBLIC_ESIGNER_BASE_URL;
if (!esignerUrl) {
throw new Error("PUBLIC_ESIGNER_BASE_URL is not configured");
}
const fileLink = `${esignerUrl}/files/${encodeURIComponent(file.id)}`;
🤖 Prompt for AI Agents
In @platforms/esigner-api/src/services/NotificationService.ts around lines 288 -
289, PUBLIC_ESIGNER_BASE_URL must not default to localhost and file IDs must be
URL-encoded: remove the '|| "http://localhost:3004"' fallback when building
esignerUrl in NotificationService (where fileLink is constructed) and instead
validate that process.env.PUBLIC_ESIGNER_BASE_URL is present (throw or surface a
clear error if missing); when building fileLink use an encoded ID (e.g.,
encodeURIComponent(file.id)) so special characters are safe. Apply the same
change where the same pattern appears in NotificationService (other occurrences
around lines 319 and 347) and in SignatureService at the usage on line 75.


return `📝 Signature Invitation

Expand All @@ -296,7 +298,7 @@ Size: ${this.formatFileSize(file.size)}
Type: ${file.mimeType}
Time: ${formattedTime}

Please review and sign the signature container when ready.`;
<a href="${fileLink}">View Signature Container in eSigner</a>`;
}

/**
Expand All @@ -314,6 +316,8 @@ Please review and sign the signature container when ready.`;
const signerText = signerName ? ` by ${signerName}` : '';
const containerName = file.displayName || file.name;
const descriptionText = file.description ? `\nDescription: ${file.description}` : '';
const esignerUrl = process.env.PUBLIC_ESIGNER_BASE_URL || 'http://localhost:3004';
const fileLink = `${esignerUrl}/files/${file.id}`;

return `✅ Signature Completed

Expand All @@ -323,7 +327,7 @@ Signature Container: ${containerName}${descriptionText}
File: ${file.name}
Time: ${formattedTime}

The signature has been recorded and verified.`;
<a href="${fileLink}">View Signature Container in eSigner</a>`;
}

/**
Expand All @@ -340,6 +344,8 @@ The signature has been recorded and verified.`;

const containerName = file.displayName || file.name;
const descriptionText = file.description ? `\nDescription: ${file.description}` : '';
const esignerUrl = process.env.PUBLIC_ESIGNER_BASE_URL || 'http://localhost:3004';
const fileLink = `${esignerUrl}/files/${file.id}`;

return `🎉 Signature Container Fully Signed

Expand All @@ -349,7 +355,7 @@ Signature Container: ${containerName}${descriptionText}
File: ${file.name}
Time: ${formattedTime}

The signature container is now complete. You can download the proof from the eSigner platform.`;
<a href="${fileLink}">Download Proof from eSigner</a>`;
}

/**
Expand Down
10 changes: 6 additions & 4 deletions platforms/esigner/.svelte-kit/generated/client/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ export const nodes = [
() => import('./nodes/4'),
() => import('./nodes/5'),
() => import('./nodes/6'),
() => import('./nodes/7')
() => import('./nodes/7'),
() => import('./nodes/8')
];

export const server_loads = [];

export const dictionary = {
"/": [3],
"/(auth)/auth": [4],
"/(protected)/files": [5,[2]],
"/(protected)/files/new": [7,[2]],
"/(protected)/files/[id]": [6,[2]]
"/(auth)/deeplink-login": [5],
"/(protected)/files": [6,[2]],
"/(protected)/files/new": [8,[2]],
"/(protected)/files/[id]": [7,[2]]
};

export const hooks = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default as component } from "../../../../src/routes/(protected)/files/+page.svelte";
export { default as component } from "../../../../src/routes/(auth)/deeplink-login/+page.svelte";
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default as component } from "../../../../src/routes/(protected)/files/[id]/+page.svelte";
export { default as component } from "../../../../src/routes/(protected)/files/+page.svelte";
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default as component } from "../../../../src/routes/(protected)/files/new/+page.svelte";
export { default as component } from "../../../../src/routes/(protected)/files/[id]/+page.svelte";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as component } from "../../../../src/routes/(protected)/files/new/+page.svelte";
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const options = {
app: ({ head, body, assets, nonce, env }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t\t" + head + "\n\t</head>\n\t<body data-sveltekit-preload-data=\"hover\">\n\t\t<div style=\"display: contents\">" + body + "</div>\n\t</body>\n</html>\n\n",
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
},
version_hash: "jgao1c"
version_hash: "he2m2"
};

export async function get_hooks() {
Expand Down
5 changes: 3 additions & 2 deletions platforms/esigner/.svelte-kit/non-ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export {};

declare module "$app/types" {
export interface AppTypes {
RouteId(): "/(protected)" | "/(auth)" | "/" | "/(auth)/auth" | "/(protected)/files" | "/(protected)/files/new" | "/(protected)/files/[id]";
RouteId(): "/(protected)" | "/(auth)" | "/" | "/(auth)/auth" | "/(auth)/deeplink-login" | "/(protected)/files" | "/(protected)/files/new" | "/(protected)/files/[id]";
RouteParams(): {
"/(protected)/files/[id]": { id: string }
};
Expand All @@ -36,11 +36,12 @@ declare module "$app/types" {
"/(auth)": Record<string, never>;
"/": { id?: string };
"/(auth)/auth": Record<string, never>;
"/(auth)/deeplink-login": Record<string, never>;
"/(protected)/files": { id?: string };
"/(protected)/files/new": Record<string, never>;
"/(protected)/files/[id]": { id: string }
};
Pathname(): "/" | "/auth" | "/auth/" | "/files" | "/files/" | "/files/new" | "/files/new/" | `/files/${string}` & {} | `/files/${string}/` & {};
Pathname(): "/" | "/auth" | "/auth/" | "/deeplink-login" | "/deeplink-login/" | "/files" | "/files/" | "/files/new" | "/files/new/" | `/files/${string}` & {} | `/files/${string}/` & {};
ResolvedPathname(): `${"" | `/${string}`}${ReturnType<AppTypes['Pathname']>}`;
Asset(): string & {};
}
Expand Down
1 change: 1 addition & 0 deletions platforms/esigner/.svelte-kit/types/route_meta_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"/(protected)": [],
"/": [],
"/(auth)/auth": [],
"/(auth)/deeplink-login": [],
"/(protected)/files": [],
"/(protected)/files/new": [],
"/(protected)/files/[id]": []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type EnsureDefined<T> = T extends null | undefined ? {} : T;
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
export type Snapshot<T = any> = Kit.Snapshot<T>;
type PageParentData = EnsureDefined<LayoutData>;
type LayoutRouteId = RouteId | "/" | "/(auth)/auth" | "/(protected)/files" | "/(protected)/files/[id]" | "/(protected)/files/new" | null
type LayoutRouteId = RouteId | "/" | "/(auth)/auth" | "/(auth)/deeplink-login" | "/(protected)/files" | "/(protected)/files/[id]" | "/(protected)/files/new" | null
type LayoutParams = RouteParams & { id?: string }
type LayoutParentData = EnsureDefined<{}>;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type * as Kit from '@sveltejs/kit';

type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
// @ts-ignore
type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;
type RouteParams = { };
type RouteId = '/(auth)/deeplink-login';
type MaybeWithVoid<T> = {} extends T ? T | void : T;
export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];
type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>
type EnsureDefined<T> = T extends null | undefined ? {} : T;
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
export type Snapshot<T = any> = Kit.Snapshot<T>;
type PageParentData = EnsureDefined<import('../../$types.js').LayoutData>;

export type PageServerData = null;
export type PageData = Expand<PageParentData>;
export type PageProps = { params: RouteParams; data: PageData }
10 changes: 10 additions & 0 deletions platforms/esigner/src/lib/utils/mobile-detection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function isMobileDevice(): boolean {
if (typeof window === 'undefined') return false;

return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
(window.innerWidth <= 768);
}
Comment on lines +1 to +6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Mixed detection methods may cause inconsistent mobile identification.

Combining user agent regex with viewport width (<= 768px) means a resized desktop browser will be treated as mobile, while a tablet in landscape mode might not be. This can lead to unexpected UX inconsistencies.

Consider separating these concerns or choosing one primary detection method for more predictable behavior.

🤖 Prompt for AI Agents
In @platforms/esigner/src/lib/utils/mobile-detection.ts around lines 1 - 6, The
isMobileDevice function mixes user-agent and viewport checks causing
inconsistent results; split into two clear helpers (e.g., isMobileByUserAgent
and isMobileByViewport) and update callers to choose the appropriate strategy or
add an optional parameter to isMobileDevice(mode: 'ua'|'viewport'|'hybrid') that
selects the detection method; ensure isMobileByUserAgent uses the existing regex
against navigator.userAgent and isMobileByViewport uses a configurable
breakpoint (default 768) against window.innerWidth, and remove the current
unconditional OR combination in isMobileDevice so behavior is predictable.


export function getDeepLinkUrl(qrData: string): string {
return qrData;
}
Comment on lines +8 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove unnecessary passthrough function.

The getDeepLinkUrl function simply returns its input without any transformation, validation, or logic. This adds no value and creates unnecessary indirection.

🔎 Proposed fix to remove the passthrough function

Remove the function from this file:

-export function getDeepLinkUrl(qrData: string): string {
-	return qrData;
-}

Then update the import and usage in platforms/esigner/src/routes/(protected)/files/[id]/+page.svelte:

-import { isMobileDevice, getDeepLinkUrl } from '$lib/utils/mobile-detection';
+import { isMobileDevice } from '$lib/utils/mobile-detection';

And replace the function call with direct usage:

 <a
-  href={getDeepLinkUrl(signingSession.qrData)}
+  href={signingSession.qrData}
   class="w-full px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-center font-medium"
 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function getDeepLinkUrl(qrData: string): string {
return qrData;
}
🤖 Prompt for AI Agents
In @platforms/esigner/src/lib/utils/mobile-detection.ts around lines 8 - 10, The
getDeepLinkUrl passthrough function should be removed: delete the exported
function getDeepLinkUrl(qrData: string) from mobile-detection.ts, remove its
import wherever used (e.g., the component that imports getDeepLinkUrl), and
replace calls to getDeepLinkUrl(qrData) with the direct qrData value (or the
original variable) since no transformation/validation occurs; update
imports/exports accordingly to avoid unused-import errors.

112 changes: 112 additions & 0 deletions platforms/esigner/src/routes/(auth)/deeplink-login/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<script lang="ts">
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { apiClient } from '$lib/utils/axios';
import { login } from '$lib/stores/auth';
let isLoading = $state(true);
let error = $state<string | null>(null);
onMount(() => {
const handleDeeplinkLogin = async () => {
try {
// Try parsing from search string first
let params: URLSearchParams;
let searchString = window.location.search;
// If search is empty, try parsing from hash or full URL
if (!searchString || searchString === '') {
const hash = window.location.hash;
if (hash && hash.includes('?')) {
searchString = hash.substring(hash.indexOf('?'));
} else {
try {
const fullUrl = new URL(window.location.href);
searchString = fullUrl.search;
} catch (e) {
// Ignore parsing errors
}
}
}
// Remove leading ? if present
if (searchString.startsWith('?')) {
searchString = searchString.substring(1);
}
// Parse the search string
params = new URLSearchParams(searchString);
let ename = params.get('ename');
let session = params.get('session');
let signature = params.get('signature');
const appVersion = params.get('appVersion');
if (!ename || !session || !signature) {
// Add a small delay to allow URL to fully parse before showing error
await new Promise(resolve => setTimeout(resolve, 500));
// Re-check one more time after delay
const finalParams = new URLSearchParams(window.location.search || (window.location.hash.includes('?') ? window.location.hash.substring(window.location.hash.indexOf('?') + 1) : '') || '');
ename = finalParams.get('ename') || ename;
session = finalParams.get('session') || session;
signature = finalParams.get('signature') || signature;
if (!ename || !session || !signature) {
error = "Missing required authentication parameters";
isLoading = false;
return;
}
}
Comment on lines +45 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Investigate the root cause instead of using delay-based retry.

Same timing/retry issue as in file-manager. The 500ms delay workaround is unreliable and should be replaced with proper URL parameter handling using SvelteKit's page store.

See the file-manager review for the detailed alternative approach.

🤖 Prompt for AI Agents
In @platforms/esigner/src/routes/(auth)/deeplink-login/+page.svelte around lines
43 - 57, The 500ms delay retry around reading ename, session, and signature is
unreliable; replace it by reading URL params from SvelteKit's page store instead
of window.location (use the page store or the load function), locate the block
that references ename, session, signature and the URLSearchParams fallback, and
change it to derive these values from $page.url.searchParams or from the load
context so you get deterministic params on navigation; remove the setTimeout/new
Promise retry and ensure isLoading/error flow uses the store-derived values.

// Clean up URL
window.history.replaceState({}, '', window.location.pathname);
// Make POST request to login endpoint using apiClient
const requestBody = { ename, session, signature, appVersion: appVersion || '0.4.0' };
const response = await apiClient.post('/api/auth', requestBody);
if (response.data.token && response.data.user) {
login(response.data.token, response.data.user);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find platforms/esigner/src/lib/stores -name "*.ts" -o -name "*.js" -o -name "*.svelte" | head -20

Repository: MetaState-Prototype-Project/prototype

Length of output: 300


🏁 Script executed:

fd "auth" platforms/esigner/src/lib/stores --type f

Repository: MetaState-Prototype-Project/prototype

Length of output: 121


🏁 Script executed:

cat platforms/esigner/src/lib/stores/auth.ts 2>/dev/null || cat platforms/esigner/src/lib/stores/auth.js 2>/dev/null || echo "auth store not found in expected locations"

Repository: MetaState-Prototype-Project/prototype

Length of output: 2223


🏁 Script executed:

cat platforms/esigner/src/lib/utils/axios.ts 2>/dev/null || cat platforms/esigner/src/lib/utils/axios.js 2>/dev/null

Repository: MetaState-Prototype-Project/prototype

Length of output: 781


Authentication tokens are stored in localStorage, creating an XSS vulnerability.

The login() function calls setAuthToken() (in $lib/utils/axios.ts:18) which directly stores the authentication token in localStorage via localStorage.setItem('esigner_auth_token', token). Tokens stored in localStorage are accessible to any JavaScript executing on the page, making them vulnerable to XSS attacks.

Migrate to httpOnly cookies set by the server instead. This prevents JavaScript from accessing the token, significantly improving security posture.

goto('/files');
} else {
error = "Invalid response from server";
isLoading = false;
}
} catch (err: any) {
console.error('Login request failed:', err);
if (err.response?.data?.error) {
error = err.response.data.error;
} else if (err.response?.status) {
error = `Server error: ${err.response.status}`;
} else {
error = "Failed to connect to server";
}
isLoading = false;
}
};
handleDeeplinkLogin();
});
</script>

{#if isLoading}
<div class="flex h-screen items-center justify-center">
<div class="text-center">
<div class="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p class="text-lg text-gray-600">Authenticating...</p>
</div>
</div>
{:else if error}
<div class="flex h-screen items-center justify-center">
<div class="text-center">
<div class="text-red-600 mb-4">{error}</div>
<button
onclick={() => window.location.href = "/"}
class="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Go to Login
</button>
</div>
</div>
{/if}
Loading