Skip to content

Commit d53a70e

Browse files
committed
redo the auth so that only one user acc is created
1 parent 55aaef1 commit d53a70e

File tree

4 files changed

+163
-57
lines changed

4 files changed

+163
-57
lines changed

apps/obsidian/src/utils/supabaseContext.ts

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ export type SupabaseContext = {
1919
let contextCache: SupabaseContext | null = null;
2020

2121
const generateAccountLocalId = (vaultName: string): string => {
22-
const randomSuffix = Math.random()
23-
.toString(36)
24-
.substring(2, 8)
25-
.toUpperCase();
26-
return `${vaultName}-${randomSuffix}`;
22+
const randomSuffix = Math.random().toString(36).substring(2, 8).toUpperCase();
23+
const sanitizedVaultName = vaultName
24+
.replace(/\s+/g, "")
25+
.replace(/[^a-zA-Z0-9]/g, "")
26+
.replace(/-+/g, "-");
27+
return `${sanitizedVaultName}${randomSuffix}@database.discoursegraphs.com`;
2728
};
2829

2930
const getOrCreateSpacePassword = async (
@@ -71,6 +72,8 @@ export const getSupabaseContext = async (
7172
url,
7273
name: vaultName,
7374
platform,
75+
accountLocalId,
76+
accountName: vaultName,
7477
});
7578

7679
if (!spaceResult.data) {
@@ -107,19 +110,55 @@ let loggedInClient: DGSupabaseClient | null = null;
107110
export const getLoggedInClient = async (
108111
plugin: DiscourseGraphPlugin,
109112
): Promise<DGSupabaseClient | null> => {
113+
const accountLocalId = plugin.settings.accountLocalId;
114+
if (!accountLocalId) {
115+
throw new Error("accountLocalId not found in plugin settings");
116+
}
110117
if (loggedInClient === null) {
111118
const context = await getSupabaseContext(plugin);
112-
if (context === null) throw new Error("Could not create context");
113-
loggedInClient = await createLoggedInClient(
114-
context.platform,
115-
context.spaceId,
116-
context.spacePassword,
117-
);
119+
if (context === null) {
120+
throw new Error("Could not create Supabase context");
121+
}
122+
try {
123+
loggedInClient = await createLoggedInClient({
124+
platform: context.platform,
125+
spaceId: context.spaceId,
126+
password: context.spacePassword,
127+
accountLocalId,
128+
});
129+
if (!loggedInClient) {
130+
throw new Error(
131+
"Failed to create Supabase client - check environment variables",
132+
);
133+
}
134+
} catch (error) {
135+
const errorMessage =
136+
error instanceof Error ? error.message : String(error);
137+
console.error("Failed to create logged-in client:", errorMessage);
138+
throw new Error(`Supabase authentication failed: ${errorMessage}`);
139+
}
118140
} else {
119141
// renew session
120142
const { error } = await loggedInClient.auth.getSession();
121143
if (error) {
144+
console.warn("Session renewal failed, re-authenticating:", error);
122145
loggedInClient = null;
146+
const context = await getSupabaseContext(plugin);
147+
if (context === null) {
148+
throw new Error(
149+
"Could not create Supabase context for re-authentication",
150+
);
151+
}
152+
153+
loggedInClient = await createLoggedInClient({
154+
platform: context.platform,
155+
spaceId: context.spaceId,
156+
password: context.spacePassword,
157+
accountLocalId,
158+
});
159+
if (!loggedInClient) {
160+
throw new Error("Failed to re-authenticate Supabase client");
161+
}
123162
}
124163
}
125164
return loggedInClient;

apps/roam/src/utils/supabaseContext.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,11 @@ export const getLoggedInClient = async (): Promise<DGSupabaseClient | null> => {
9696
if (_loggedInClient === null) {
9797
const context = await getSupabaseContext();
9898
if (context === null) throw new Error("Could not create context");
99-
_loggedInClient = await createLoggedInClient(
100-
context.platform,
101-
context.spaceId,
102-
context.spacePassword,
103-
);
99+
_loggedInClient = await createLoggedInClient({
100+
platform: context.platform,
101+
spaceId: context.spaceId,
102+
password: context.spacePassword,
103+
});
104104
} else {
105105
// renew session
106106
const { error } = await _loggedInClient.auth.getSession();

packages/database/src/lib/contextFunctions.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ const baseUrl = nextApiRoot() + "/supabase";
1414
type SpaceDataInput = TablesInsert<"Space">;
1515
export type SpaceRecord = Tables<"Space">;
1616

17-
export type SpaceCreationInput = SpaceDataInput & { password: string };
17+
export type SpaceCreationInput = SpaceDataInput & {
18+
password: string;
19+
accountLocalId?: string;
20+
accountName?: string;
21+
};
1822

1923
export const asPostgrestFailure = (
2024
message: string,
@@ -123,15 +127,25 @@ export const fetchOrCreateSpaceDirect = async (
123127
};
124128
};
125129

126-
export const createLoggedInClient = async (
127-
platform: Platform,
128-
spaceId: number,
129-
password: string,
130-
): Promise<DGSupabaseClient | null> => {
130+
export const createLoggedInClient = async ({
131+
platform,
132+
spaceId,
133+
password,
134+
accountLocalId,
135+
}: {
136+
platform: Platform;
137+
spaceId: number;
138+
password: string;
139+
accountLocalId?: string;
140+
}): Promise<DGSupabaseClient | null> => {
131141
const loggedInClient: DGSupabaseClient | null = createClient();
132142
if (!loggedInClient) return null;
143+
const email =
144+
platform === "Obsidian" && accountLocalId
145+
? accountLocalId
146+
: spaceAnonUserEmail(platform, spaceId);
133147
const { error } = await loggedInClient.auth.signInWithPassword({
134-
email: spaceAnonUserEmail(platform, spaceId),
148+
email,
135149
password: password,
136150
});
137151
if (error) {
@@ -155,9 +169,28 @@ export const fetchOrCreatePlatformAccount = async ({
155169
spaceId: number;
156170
password: string;
157171
}): Promise<number> => {
158-
const supabase = await createLoggedInClient(platform, spaceId, password);
172+
const supabase = await createLoggedInClient({
173+
platform,
174+
spaceId,
175+
password,
176+
accountLocalId,
177+
});
159178
if (!supabase) throw Error("Missing database connection");
160179

180+
// For Obsidian, user account is already created in create-space, so just fetch it
181+
if (platform === "Obsidian") {
182+
const result = await supabase
183+
.from("PlatformAccount")
184+
.select("id")
185+
.eq("account_local_id", accountLocalId)
186+
.eq("platform", platform)
187+
.single();
188+
if (result.error) throw Error(result.error.message);
189+
if (!result.data) throw Error("Account not found");
190+
return result.data.id;
191+
}
192+
193+
// For Roam, use the existing flow
161194
const result = await supabase.rpc("create_account_in_space", {
162195
space_id_: spaceId,
163196
account_local_id_: accountLocalId,

packages/database/supabase/functions/create-space/index.ts

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const asPostgrestFailure = <T>(
5151
const spaceValidator = (space: SpaceCreationInput): string | null => {
5252
if (!space || typeof space !== "object")
5353
return "Invalid request body: expected a JSON object.";
54-
const { name, url, platform, password } = space;
54+
const { name, url, platform, password, accountLocalId, accountName } = space;
5555

5656
if (!name || typeof name !== "string" || name.trim() === "")
5757
return "Missing or invalid name.";
@@ -61,6 +61,12 @@ const spaceValidator = (space: SpaceCreationInput): string | null => {
6161
return "Missing or invalid platform.";
6262
if (!password || typeof password !== "string" || password.length < 8)
6363
return "password must be at least 8 characters";
64+
if (platform === "Obsidian") {
65+
if (!accountLocalId || typeof accountLocalId !== "string")
66+
return "Missing or invalid accountLocalId for Obsidian platform.";
67+
if (!accountName || typeof accountName !== "string")
68+
return "Missing or invalid accountName for Obsidian platform.";
69+
}
6470
return null;
6571
};
6672

@@ -95,62 +101,90 @@ const processAndGetOrCreateSpace = async (
95101
const space_id = result.data.id;
96102

97103
// this is related but each step is idempotent, so con retry w/o transaction
98-
const email = spaceAnonUserEmail(platform, result.data.id);
99-
let anonymousUser: User | null = null;
104+
// For Obsidian: create real user account (not anonymous)
105+
// For Roam: create anonymous account
106+
let email: string;
107+
let accountLocalIdForStorage: string;
108+
let accountName: string;
109+
let agentType: "person" | "anonymous";
110+
111+
if (platform === "Obsidian") {
112+
if (!data.accountLocalId || !data.accountName) {
113+
return asPostgrestFailure<SpaceRecord>(
114+
"accountLocalId and accountName are required for Obsidian platform",
115+
"invalid space",
116+
);
117+
}
118+
email = data.accountLocalId;
119+
accountLocalIdForStorage = data.accountLocalId;
120+
accountName = data.accountName;
121+
agentType = "person";
122+
} else {
123+
email = spaceAnonUserEmail(platform, result.data.id);
124+
accountLocalIdForStorage = email;
125+
accountName = `Anonymous of space ${space_id}`;
126+
agentType = "anonymous";
127+
}
128+
129+
let authUser: User | null = null;
100130
{
101-
const { error, data } = await supabase.auth.signInWithPassword({
102-
email,
103-
password,
104-
});
131+
const { error: signInError, data: signInData } =
132+
await supabase.auth.signInWithPassword({
133+
email,
134+
password,
135+
});
105136

106137
if (
107-
error &&
138+
signInError &&
108139
!(
109-
error.code === "invalid_credentials" ||
110-
error.message === "Invalid login credentials"
140+
signInError.code === "invalid_credentials" ||
141+
signInError.message === "Invalid login credentials"
111142
)
112143
) {
113144
// Handle unexpected errors
114-
return asPostgrestFailure(error.message, "authentication_error");
145+
return asPostgrestFailure(signInError.message, "authentication_error");
115146
}
116-
anonymousUser = data.user;
147+
authUser = signInData?.user ?? null;
117148
await supabase.auth.signOut({ scope: "local" });
118149
}
119-
if (anonymousUser === null) {
120-
const resultCreateAnonymousUser = await supabase.auth.admin.createUser({
150+
151+
if (authUser === null) {
152+
const resultCreateUser = await supabase.auth.admin.createUser({
121153
email,
122154
password,
123155
email_confirm: true,
124156
});
125-
if (resultCreateAnonymousUser.error) {
157+
if (resultCreateUser.error) {
126158
return {
127159
count: null,
128-
status: resultCreateAnonymousUser.error.status || -1,
129-
statusText: resultCreateAnonymousUser.error.message,
160+
status: resultCreateUser.error.status || -1,
161+
statusText: resultCreateUser.error.message,
130162
data: null,
131163
error: new PostgrestError({
132-
message: resultCreateAnonymousUser.error.message,
164+
message: resultCreateUser.error.message,
133165
details:
134-
typeof resultCreateAnonymousUser.error.cause === "string"
135-
? resultCreateAnonymousUser.error.cause
166+
typeof resultCreateUser.error.cause === "string"
167+
? resultCreateUser.error.cause
136168
: "",
137169
hint: "",
138-
code: resultCreateAnonymousUser.error.code || "unknown",
170+
code: resultCreateUser.error.code || "unknown",
139171
}),
140-
}; // space created but not its user, try again
172+
};
141173
}
142-
anonymousUser = resultCreateAnonymousUser.data.user as User;
174+
authUser = resultCreateUser.data.user as User;
143175
}
144-
// NOTE: The next few steps could be done as the new user, except the SpaceAccess
145-
const anonPlatformUserResult = await supabase
176+
177+
// For Obsidian: real user with name=vaultName, agent_type=person
178+
// For Roam: anonymous account
179+
const platformAccountResult = await supabase
146180
.from("PlatformAccount")
147181
.upsert(
148182
{
149183
platform,
150-
account_local_id: email,
151-
name: `Anonymous of space ${space_id}`,
152-
agent_type: "anonymous",
153-
dg_account: anonymousUser.id,
184+
account_local_id: accountLocalIdForStorage,
185+
name: accountName,
186+
agent_type: agentType,
187+
dg_account: authUser.id,
154188
},
155189
{
156190
onConflict: "account_local_id,platform",
@@ -160,14 +194,14 @@ const processAndGetOrCreateSpace = async (
160194
)
161195
.select()
162196
.single();
163-
if (anonPlatformUserResult.error) return anonPlatformUserResult;
197+
if (platformAccountResult.error) return platformAccountResult;
164198

165-
const resultAnonUserSpaceAccess = await supabase
199+
const resultUserSpaceAccess = await supabase
166200
.from("SpaceAccess")
167201
.upsert(
168202
{
169203
space_id,
170-
account_id: anonPlatformUserResult.data.id,
204+
account_id: platformAccountResult.data.id,
171205
editor: true,
172206
},
173207
{
@@ -178,7 +212,7 @@ const processAndGetOrCreateSpace = async (
178212
)
179213
.select()
180214
.single();
181-
if (resultAnonUserSpaceAccess.error) return resultAnonUserSpaceAccess; // space created but not connected, try again
215+
if (resultUserSpaceAccess.error) return resultUserSpaceAccess; // space created but not connected, try again
182216
return result;
183217
};
184218

0 commit comments

Comments
 (0)