Skip to content
Open
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
51 changes: 1 addition & 50 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
},
"dependencies": {
"@ai-sdk/openai": "^2.0.0",
"@auth0/auth0-react": "^2.2.4",
"@lexical/react": "^0.16.1",
"@posthog/react": "^1.9.0",
"@react-hook/window-size": "^3.1.1",
Expand Down
24 changes: 0 additions & 24 deletions frontend/popup.html

This file was deleted.

4 changes: 2 additions & 2 deletions frontend/public/privacypolicy.html
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ <h3 id="usage-data">Usage Data</h3>
</ul>
<h3 id="account-information">Account Information</h3>
<ul>
<li>Basic authentication information provided through Auth0</li>
<li>Basic authentication information provided through our authentication provider</li>
<li>Email address (if you choose to provide it for support purposes)</li>
</ul>
<h2 id="how-we-use-your-information">How We Use Your Information</h2>
Expand Down Expand Up @@ -253,7 +253,7 @@ <h3 id="service-providers">Service Providers</h3>
<ul>
<li>Cloud computing infrastructure</li>
<li>AI processing capabilities</li>
<li>Authentication (Auth0)</li>
<li>Authentication (Google sign-in)</li>
<li>Microsoft Office Add-in platform</li>
</ul></li>
<li>All service providers are bound by strict confidentiality agreements</li>
Expand Down
70 changes: 70 additions & 0 deletions frontend/src/api/__tests__/authTokenStore.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { clearToken, loadToken, persistToken } from '../authTokenStore';

// Minimal in-memory localStorage stub so the store can be tested in the node
// environment (no jsdom dependency).
function makeStorage() {
const map = new Map<string, string>();
return {
getItem: (k: string) => (map.has(k) ? map.get(k)! : null),
setItem: (k: string, v: string) => {
map.set(k, v);
},
removeItem: (k: string) => {
map.delete(k);
},
clear: () => map.clear(),
};
}

beforeEach(() => {
vi.stubGlobal('localStorage', makeStorage());
});

afterEach(() => {
vi.unstubAllGlobals();
});

describe('authTokenStore', () => {
it('persists and loads a token round-trip', () => {
expect(loadToken()).toBeNull();
persistToken('tok-123');
expect(loadToken()).toBe('tok-123');
});

it('clears the token', () => {
persistToken('tok-123');
clearToken();
expect(loadToken()).toBeNull();
});

it('degrades gracefully when setItem throws (no token persisted)', () => {
vi.stubGlobal('localStorage', {
...makeStorage(),
setItem: () => {
throw new Error('storage disabled');
},
});
expect(() => persistToken('tok-123')).not.toThrow();
});

it('returns null when getItem throws', () => {
vi.stubGlobal('localStorage', {
...makeStorage(),
getItem: () => {
throw new Error('storage disabled');
},
});
expect(loadToken()).toBeNull();
});

it('does not throw when removeItem throws', () => {
vi.stubGlobal('localStorage', {
...makeStorage(),
removeItem: () => {
throw new Error('storage disabled');
},
});
expect(() => clearToken()).not.toThrow();
});
});
39 changes: 39 additions & 0 deletions frontend/src/api/authTokenStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Persisted Better Auth token store.
*
* Keeps the device-flow access token across page refreshes so Better Auth can be the
* default auth without forcing a fresh interactive login on every reload.
*
* Every localStorage access is guarded: Office task panes and other embedded browsers
* can throw on storage access under some privacy/host settings. On failure we degrade to
* in-memory-only for the session (load returns null; persist/clear become no-ops) so the
* app keeps working.
*
* Security note: localStorage is XSS-exposed. This matches Auth0's prior
* `cacheLocation="localstorage"` posture, so it does not raise the existing risk level.
*/
const TOKEN_KEY = 'betterauth.token';

export function persistToken(token: string): void {
try {
localStorage.setItem(TOKEN_KEY, token);
} catch {
// Storage unavailable — fall back to in-memory for this session.
}
}

export function loadToken(): string | null {
try {
return localStorage.getItem(TOKEN_KEY);
} catch {
return null;
}
}

export function clearToken(): void {
try {
localStorage.removeItem(TOKEN_KEY);
} catch {
// No-op if storage is unavailable.
}
}
83 changes: 1 addition & 82 deletions frontend/src/api/googleDocsEditorAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
* all document operations here go through Apps Script on Google's servers.
*/

import type { Auth0ContextInterface } from '@auth0/auth0-react';

// Declare the global GoogleAppsScript bridge (defined in sidebar.html)
declare global {
interface Window {
Expand Down Expand Up @@ -110,85 +108,6 @@ function stopPolling() {
* Google Docs implementation of the EditorAPI interface.
*/
export const googleDocsEditorAPI: EditorAPI = {
/**
* Handles login for Google Docs.
* Since users are already authenticated with Google, we use their Google identity.
* For Auth0 integration, we could implement a popup flow similar to Word.
*/
async doLogin(auth0Client: Auth0ContextInterface): Promise<void> {
// Option 1: Use Google identity directly (simpler)
// The user is already logged into Google, so we can use their email
// as the identifier and skip Auth0 entirely for Google Docs.

// Option 2: Implement Auth0 popup flow (for consistency with Word)
// This would require opening a popup window and handling the OAuth flow.

// For now, we'll use a simplified approach that works with Google identity
console.log(
'Google Docs login: Using Google identity. Auth0 integration pending.',
);

// If Auth0 is required, we could implement a similar popup flow:
// 1. Open a popup to Auth0 login URL
// 2. Have the popup redirect back with tokens
// 3. Store tokens via Apps Script user properties

// Placeholder: trigger Auth0 login if needed
try {
await auth0Client.loginWithRedirect({
openUrl: (url: string) => {
// Open in a new window since we can't do redirects in an iframe
const popup = window.open(
url,
'auth0-login',
'width=500,height=600',
);

// Poll for completion (the popup should post a message when done)
const pollTimer = setInterval(() => {
if (popup?.closed) {
clearInterval(pollTimer);
// Check if we're now logged in
auth0Client.getAccessTokenSilently().catch(() => {
console.log('Auth0 login was cancelled or failed');
});
}
}, 500);
},
});
} catch (error) {
console.error('Auth0 login error:', error);
}
},

/**
* Handles logout for Google Docs.
*/
async doLogout(auth0Client: Auth0ContextInterface): Promise<void> {
try {
await auth0Client.logout({
openUrl: (url: string) => {
// Open logout URL in a popup
const popup = window.open(
url,
'auth0-logout',
'width=500,height=400',
);

// Close popup after a brief delay
setTimeout(() => {
popup?.close();
}, 2000);
},
logoutParams: {
returnTo: window.location.origin,
},
});
} catch (error) {
console.error('Auth0 logout error:', error);
}
},

/**
* Adds a handler for selection changes.
* Uses polling since Google Docs doesn't provide native selection events.
Expand Down Expand Up @@ -246,7 +165,7 @@ export function isRunningInGoogleDocs(): boolean {

/**
* Gets the current user's email from Google.
* This can be used as a fallback identifier if Auth0 is not configured.
* Used as the identifier for the Google Docs surface (which runs in demo mode).
*/
export async function getGoogleUserEmail(): Promise<string | null> {
if (!isRunningInGoogleDocs()) {
Expand Down
Loading