Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Add authentication infrastructure #316

Merged
merged 20 commits into from
Sep 11, 2024
Merged
9 changes: 9 additions & 0 deletions app/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export enum WebviewChannels {
}

export enum MainChannels {
OPEN_EXTERNAL_WINDOW = 'open-external-window',

// Code
GET_CODE_BLOCK = 'get-code-block',
GET_CODE_BLOCKS = 'get-code-blocks',
Expand All @@ -61,6 +63,12 @@ export enum MainChannels {
// Ast
GET_TEMPLATE_NODE_AST = 'get-template-node-ast',
GET_TEMPLATE_NODE_CHILD = 'get-template-node-child',

// Auth
USER_SIGNED_IN = 'user-signed-in',
USER_SIGNED_OUT = 'user-signed-out',
GET_USER_METADATA = 'get-user-metadata',
SIGN_OUT = 'sign-out',
}

export enum Links {
Expand All @@ -72,6 +80,7 @@ export enum Links {
}

export const APP_NAME = 'Onlook';
export const APP_SCHEMA = 'onlook';

export const DefaultSettings = {
SCALE: 0.6,
Expand Down
16 changes: 16 additions & 0 deletions app/common/models/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,19 @@ export interface ProjectSettings {
frames?: FrameSettings[];
position?: RectPosition;
}

export interface UserMetadata {
id: string;
name?: string;
email?: string;
avatarUrl?: string;
}

export interface AuthTokens {
accessToken: string;
refreshToken: string;
expiresAt: string;
expiresIn: string;
providerToken: string;
tokenType: string;
}
File renamed without changes.
4 changes: 2 additions & 2 deletions app/electron/main/analytics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Analytics {
}

restoreSettings() {
const settings = PersistenStorage.USER_SETTINGS.read();
const settings = PersistenStorage.USER_SETTINGS.read() || {};
const enable = settings.enableAnalytics;
this.id = settings.id;
if (!this.id) {
Expand All @@ -33,7 +33,7 @@ class Analytics {
}

toggleSetting(enable: boolean) {
const settings = PersistenStorage.USER_SETTINGS.read();
const settings = PersistenStorage.USER_SETTINGS.read() || {};
if (settings.enableAnalytics === enable) {
return;
}
Expand Down
75 changes: 75 additions & 0 deletions app/electron/main/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { User } from '@supabase/supabase-js';
import { mainWindow } from '..';
import { PersistenStorage } from '../storage';
import { APP_SCHEMA, MainChannels } from '/common/constants';
import { AuthTokens, UserMetadata } from '/common/models/settings';
import supabase from '/common/supabase';

export async function handleAuthCallback(url: string) {
if (!url.startsWith(APP_SCHEMA + '://auth')) {
return;
}

const authTokens = getToken(url);
PersistenStorage.AUTH_TOKENS.write(authTokens);

if (!supabase) {
throw new Error('No backend connected');
}

const {
data: { user },
error,
} = await supabase.auth.getUser(authTokens.accessToken);

if (error) {
throw error;
}

if (!user) {
throw new Error('No user found');
}

const userMetadata = getUserMetadata(user);
PersistenStorage.USER_METADATA.write(userMetadata);

emitAuthEvent();
}

function emitAuthEvent() {
mainWindow?.webContents.send(MainChannels.USER_SIGNED_IN);
}

function getToken(url: string): AuthTokens {
const fragmentParams = new URLSearchParams(url.split('#')[1]);

const accessToken = fragmentParams.get('access_token');
const refreshToken = fragmentParams.get('refresh_token');
const expiresAt = fragmentParams.get('expires_at');
const expiresIn = fragmentParams.get('expires_in');
const providerToken = fragmentParams.get('provider_token');
const tokenType = fragmentParams.get('token_type');

if (!accessToken || !refreshToken || !expiresAt || !expiresIn || !providerToken || !tokenType) {
throw new Error('Invalid token');
}

return {
accessToken,
refreshToken,
expiresAt,
expiresIn,
providerToken,
tokenType,
};
}

function getUserMetadata(user: User): UserMetadata {
const userMetadata: UserMetadata = {
id: user.id,
email: user.email,
name: user.user_metadata.full_name,
avatarUrl: user.user_metadata.avatar_url,
};
return userMetadata;
}
2 changes: 1 addition & 1 deletion app/electron/main/code/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export async function writeCode(codeDiffs: CodeDiff[]): Promise<boolean> {
}

function getIdeFromUserSettings(): IDE {
const userSettings = PersistenStorage.USER_SETTINGS.read();
const userSettings = PersistenStorage.USER_SETTINGS.read() || {};
return IDE.fromType(userSettings.ideType || IdeType.VS_CODE);
}

Expand Down
13 changes: 13 additions & 0 deletions app/electron/main/events/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ipcMain } from 'electron';
import { mainWindow } from '..';
import { PersistenStorage } from '../storage';
import { MainChannels } from '/common/constants';

export function listenForAuthMessages() {
ipcMain.handle(MainChannels.SIGN_OUT, (e: Electron.IpcMainInvokeEvent, args) => {
PersistenStorage.USER_METADATA.clear();
PersistenStorage.AUTH_TOKENS.clear();

mainWindow?.webContents.send(MainChannels.USER_SIGNED_OUT);
});
}
2 changes: 1 addition & 1 deletion app/electron/main/events/code.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ipcMain } from 'electron';
import { openInIde, pickDirectory, readCodeBlock, readCodeBlocks, writeCode } from '../code/';
import { extractComponentsFromDirectory } from '../code/components';
import { getCodeDiffs } from '../code/diff';
import { getTemplateNodeChild } from '../code/templateNode';
import { MainChannels } from '/common/constants';
import { CodeDiff, CodeDiffRequest } from '/common/models/code';
import { TemplateNode } from '/common/models/element/templateNode';
import { extractComponentsFromDirectory } from '../code/components';

export function listenForCodeMessages() {
ipcMain.handle(MainChannels.VIEW_SOURCE_CODE, (e: Electron.IpcMainInvokeEvent, args) => {
Expand Down
18 changes: 16 additions & 2 deletions app/electron/main/events/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import { ipcMain, shell } from 'electron';
import { listenForAnalyticsMessages } from './analytics';
import { listenForAuthMessages } from './auth';
import { listenForCodeMessages } from './code';
import { listenForSettingMessages } from './settings';
import { listenForStorageMessages } from './storage';
import { listenForTunnelMessages } from './tunnel';
import { MainChannels } from '/common/constants';

export function listenForIpcMessages() {
listenForGeneralMessages();
listenForTunnelMessages();
listenForAnalyticsMessages();
listenForCodeMessages();
listenForSettingMessages();
listenForStorageMessages();
listenForAuthMessages();
}

function listenForGeneralMessages() {
ipcMain.handle(
MainChannels.OPEN_EXTERNAL_WINDOW,
(e: Electron.IpcMainInvokeEvent, args: string) => {
return shell.openExternal(args);
},
);
}
21 changes: 0 additions & 21 deletions app/electron/main/events/settings.ts

This file was deleted.

32 changes: 32 additions & 0 deletions app/electron/main/events/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ipcMain } from 'electron';
import { PersistenStorage } from '../storage';
import { MainChannels } from '/common/constants';
import { ProjectSettings, UserSettings } from '/common/models/settings';

export function listenForStorageMessages() {
ipcMain.handle(MainChannels.GET_USER_SETTINGS, (e: Electron.IpcMainInvokeEvent) => {
return PersistenStorage.USER_SETTINGS.read();
});

ipcMain.handle(
MainChannels.UPDATE_USER_SETTINGS,
(e: Electron.IpcMainInvokeEvent, args: UserSettings) => {
PersistenStorage.USER_SETTINGS.update(args);
},
);

ipcMain.handle(MainChannels.GET_PROJECT_SETTINGS, (e: Electron.IpcMainInvokeEvent) => {
return PersistenStorage.PROJECT_SETTINGS.read();
});

ipcMain.handle(
MainChannels.UPDATE_PROJECT_SETTINGS,
(e: Electron.IpcMainInvokeEvent, args: ProjectSettings) => {
PersistenStorage.PROJECT_SETTINGS.update(args);
},
);

ipcMain.handle(MainChannels.GET_USER_METADATA, (e: Electron.IpcMainInvokeEvent) => {
return PersistenStorage.USER_METADATA.read();
});
}
Loading