mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-24 13:37:34 +03:00
user login page and storage
This commit is contained in:
parent
51bae9bf36
commit
7d86eb365e
19
src-tauri/Cargo.lock
generated
19
src-tauri/Cargo.lock
generated
@ -2110,6 +2110,16 @@ version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8"
|
||||
dependencies = [
|
||||
"pathdiff",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.45"
|
||||
@ -2215,6 +2225,12 @@ version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
@ -3327,9 +3343,11 @@ dependencies = [
|
||||
"ignore",
|
||||
"objc",
|
||||
"once_cell",
|
||||
"open",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"raw-window-handle",
|
||||
"regex",
|
||||
"rfd",
|
||||
"semver 1.0.16",
|
||||
"serde",
|
||||
@ -3382,6 +3400,7 @@ dependencies = [
|
||||
"png",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"semver 1.0.16",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -15,7 +15,7 @@ tauri-build = { version = "1.2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.2", features = ["dialog-open", "fs-create-dir", "fs-exists", "fs-read-file", "fs-write-file", "path-all", "system-tray", "window-start-dragging"] }
|
||||
tauri = { version = "1.2", features = ["dialog-open", "fs-create-dir", "fs-exists", "fs-read-file", "fs-write-file", "path-all", "shell-open", "system-tray", "window-start-dragging"] }
|
||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
|
||||
tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev", features = ["colored"] }
|
||||
log = "0.4.17"
|
||||
|
@ -13,6 +13,10 @@
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": false,
|
||||
"shell": {
|
||||
"all": false,
|
||||
"open": true
|
||||
},
|
||||
"dialog": {
|
||||
"all": false,
|
||||
"open": true
|
||||
@ -30,7 +34,9 @@
|
||||
"writeFile": true,
|
||||
"exists": true,
|
||||
"createDir": true,
|
||||
"scope": ["$APPLOCALDATA/databases", "$APPLOCALDATA/databases/*.json"]
|
||||
"scope": [
|
||||
"$APPLOCALDATA/user.json"
|
||||
]
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
|
59
src/lib/authentication.ts
Normal file
59
src/lib/authentication.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { dev } from '$app/environment'
|
||||
|
||||
const apiUrl = dev ? new URL('https://test.app.gitbutler.com/api/') : new URL('https://app.gitbutler.com/api/');
|
||||
|
||||
const getUrl = (path: string) => new URL(path, apiUrl).toString();
|
||||
|
||||
export type LoginToken = {
|
||||
token: string,
|
||||
expires: string,
|
||||
url: string
|
||||
}
|
||||
|
||||
export type User = {
|
||||
id: number,
|
||||
name: string,
|
||||
email: string,
|
||||
picture: string,
|
||||
locale: string,
|
||||
created_at: string,
|
||||
updated_at: string,
|
||||
access_token: string,
|
||||
}
|
||||
|
||||
const parseJSON = async (response: Response) => {
|
||||
if (response.status === 204 || response.status === 205) {
|
||||
return null;
|
||||
}
|
||||
if (response.status >= 400) {
|
||||
throw new Error(`HTTP Error ${response.statusText}: ${await response.text()}`);
|
||||
}
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
export default ({ fetch }: { fetch: typeof window.fetch } = { fetch: window.fetch }) => ({
|
||||
login: {
|
||||
token: {
|
||||
create: (params: {} = {}): Promise<LoginToken> => fetch(getUrl('login/token.json'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(params),
|
||||
}).then(parseJSON).then(token => {
|
||||
const url = new URL(token.url);
|
||||
url.host = apiUrl.host;
|
||||
return {
|
||||
...token,
|
||||
url: url.toString(),
|
||||
}
|
||||
|
||||
}),
|
||||
},
|
||||
user: {
|
||||
get: (token: string): Promise<User> => fetch(getUrl(`login/user/${token}.json`), {
|
||||
method: 'GET',
|
||||
}).then(parseJSON),
|
||||
}
|
||||
}
|
||||
})
|
@ -1,34 +0,0 @@
|
||||
import {
|
||||
exists,
|
||||
readTextFile,
|
||||
BaseDirectory,
|
||||
writeTextFile,
|
||||
createDir,
|
||||
} from "@tauri-apps/api/fs";
|
||||
import { join } from "@tauri-apps/api/path";
|
||||
|
||||
const options = {
|
||||
dir: BaseDirectory.AppLocalData,
|
||||
};
|
||||
|
||||
export const json = async <T extends any>(filename: string) => {
|
||||
await createDir("databases", { ...options, recursive: true });
|
||||
const path = await join("databases", filename);
|
||||
return {
|
||||
read: async (): Promise<T | undefined> => {
|
||||
const isExists = await exists(path, {
|
||||
dir: BaseDirectory.AppLocalData,
|
||||
});
|
||||
if (!isExists) {
|
||||
return undefined;
|
||||
} else {
|
||||
const contents = await readTextFile(path, options);
|
||||
return JSON.parse(contents) as T;
|
||||
}
|
||||
},
|
||||
|
||||
write: async (value: T): Promise<void> => {
|
||||
await writeTextFile(path, JSON.stringify(value), options);
|
||||
},
|
||||
};
|
||||
};
|
@ -1,5 +1,4 @@
|
||||
export * as deltas from "./deltas";
|
||||
export * as database from "./database";
|
||||
export * as projects from "./projects";
|
||||
export * as log from "./log";
|
||||
export * as sessions from "./sessions";
|
||||
|
32
src/lib/users.ts
Normal file
32
src/lib/users.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { BaseDirectory, exists, readTextFile, writeTextFile } from '@tauri-apps/api/fs';
|
||||
import type { User } from '$lib/authentication';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const userFile = 'user.json';
|
||||
|
||||
const isLoggedIn = () => exists(userFile, {
|
||||
dir: BaseDirectory.AppLocalData
|
||||
})
|
||||
|
||||
export default async () => {
|
||||
const store = writable<User | undefined>(undefined);
|
||||
|
||||
if (await isLoggedIn()) {
|
||||
const user = JSON.parse(await readTextFile(userFile, {
|
||||
dir: BaseDirectory.AppLocalData
|
||||
})) as User;
|
||||
store.set(user);
|
||||
}
|
||||
|
||||
store.subscribe(async (user) => {
|
||||
if (user) {
|
||||
console.log({ user });
|
||||
await writeTextFile(userFile, JSON.stringify(user), {
|
||||
dir: BaseDirectory.AppLocalData
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
return store;
|
||||
}
|
||||
|
@ -64,7 +64,7 @@
|
||||
>
|
||||
<div class="center">Search GitButler</div>
|
||||
</div>
|
||||
<div class="mr-4 cursor-default font-bold">User</div>
|
||||
<a href="/users/" class="mr-4 cursor-default font-bold">User</a>
|
||||
</header>
|
||||
|
||||
<div class="flex h-screen flex-grow flex-row text-zinc-400 overflow-hidden">
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { readable } from "svelte/store";
|
||||
import { readable, writable } from "svelte/store";
|
||||
import type { LayoutLoad } from "./$types";
|
||||
import { building } from "$app/environment";
|
||||
import type { Project } from "$lib/projects";
|
||||
@ -8,20 +8,12 @@ export const prerender = true;
|
||||
export const csr = true;
|
||||
|
||||
export const load: LayoutLoad = async () => {
|
||||
// tauri apis require window reference which doesn't exist during ssr, so we do not import it here.
|
||||
if (building) {
|
||||
return {
|
||||
projects: {
|
||||
...readable<Project[]>([]),
|
||||
add: () => {
|
||||
throw new Error("not implemented");
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const Projects = await import("$lib/projects");
|
||||
return {
|
||||
projects: await Projects.default(),
|
||||
};
|
||||
}
|
||||
const projects = building ? ({
|
||||
...readable<Project[]>([]),
|
||||
add: () => {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
}) : await (await import("$lib/projects")).default();
|
||||
const user = building ? writable<undefined>(undefined) : await (await import("$lib/users")).default();
|
||||
return { projects, user }
|
||||
};
|
||||
|
42
src/routes/users/+page.svelte
Normal file
42
src/routes/users/+page.svelte
Normal file
@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
import {open} from '@tauri-apps/api/shell'
|
||||
import Authentication, { type LoginToken } from "$lib/authentication";
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
export let data: PageData;
|
||||
const { user } = data;
|
||||
|
||||
const authApi = Authentication();
|
||||
|
||||
const pollForUser = async (token: LoginToken) => {
|
||||
await open (token.url);
|
||||
const apiUser = await authApi.login.user.get(token.token).catch(() => null);
|
||||
if (apiUser) {
|
||||
user.set(apiUser);
|
||||
return apiUser;
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(async () => {
|
||||
resolve(await pollForUser(token));
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $user}
|
||||
<div>
|
||||
Welcome, {$user.name}!
|
||||
</div>
|
||||
{:else}
|
||||
{#await authApi.login.token.create()}
|
||||
<div>loading...</div>
|
||||
{:then token}
|
||||
{#await pollForUser(token)}
|
||||
<div>Log in in your system browser</div>
|
||||
<p>If you are not redirected automatically, you can
|
||||
<button on:click={() => pollForUser(token)}>Try again</button>
|
||||
</p>
|
||||
{/await}
|
||||
{/await}
|
||||
{/if}
|
||||
|
Loading…
Reference in New Issue
Block a user