@lobb-js/lobb-ext-auth 0.1.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.zed/settings.json +22 -0
- package/deno.json +18 -0
- package/deno.lock +968 -0
- package/lobb/lobb.ts +108 -0
- package/lobb/src/collections/activityFeed.ts +53 -0
- package/lobb/src/collections/collections.ts +44 -0
- package/lobb/src/collections/sessions.ts +34 -0
- package/lobb/src/collections/users.ts +44 -0
- package/lobb/src/config/config.ts +11 -0
- package/lobb/src/config/extensionConfigSchema.ts +47 -0
- package/lobb/src/config/permissionsAction/create.ts +20 -0
- package/lobb/src/config/permissionsAction/delete.ts +3 -0
- package/lobb/src/config/permissionsAction/read.ts +10 -0
- package/lobb/src/config/permissionsAction/update.ts +20 -0
- package/lobb/src/database/init.ts +51 -0
- package/lobb/src/database/migrations.ts +56 -0
- package/lobb/src/database/utils.ts +35 -0
- package/lobb/src/extension.json +1 -0
- package/lobb/src/meta/meta.ts +11 -0
- package/lobb/src/mod.ts +27 -0
- package/lobb/src/openapi.ts +469 -0
- package/lobb/src/utils.ts +17 -0
- package/lobb/src/workflows/baseWorkflow.ts +159 -0
- package/lobb/src/workflows/hashHandlerWorkflows.ts +29 -0
- package/lobb/src/workflows/index.ts +28 -0
- package/lobb/src/workflows/meAliasWorkflows.ts +48 -0
- package/lobb/src/workflows/policiesWorkflows.ts +228 -0
- package/lobb/src/workflows/utils.ts +297 -0
- package/lobb/tests/collections/extend_users_collection.test.ts +63 -0
- package/lobb/tests/configs/auth.ts +72 -0
- package/lobb/tests/configs/auth_no_roles.ts +64 -0
- package/lobb/tests/configs/auth_public_full_access.ts +68 -0
- package/lobb/tests/configs/auth_with_different_admin_creds.ts +80 -0
- package/lobb/tests/configs/auth_with_extend_users.ts +80 -0
- package/lobb/tests/configs/auth_with_refresh_token.ts +85 -0
- package/lobb/tests/configs/auth_with_short_access_token_only.ts +94 -0
- package/lobb/tests/configs/auth_with_short_time_refresh_token.ts +85 -0
- package/lobb/tests/configs/social_blog.ts +155 -0
- package/lobb/tests/controllers/change_password.test.ts +114 -0
- package/lobb/tests/controllers/dashboardAccessRoles.test.ts +29 -0
- package/lobb/tests/controllers/login.test.ts +103 -0
- package/lobb/tests/controllers/logout.test.ts +88 -0
- package/lobb/tests/controllers/me.test.ts +275 -0
- package/lobb/tests/controllers/register.test.ts +46 -0
- package/lobb/tests/database/db.test.ts +68 -0
- package/lobb/tests/database/differentAdminCreds.test.ts +50 -0
- package/lobb/tests/middlewares/adminAuthGuard.test.ts +160 -0
- package/lobb/tests/middlewares/publicAllowBasic.test.ts +142 -0
- package/lobb/tests/middlewares/publicPreventBasic.test.ts +111 -0
- package/lobb/tests/socialBlog.test.ts +260 -0
- package/lobb/tests/utils/addArticles.ts +26 -0
- package/lobb/tests/utils/addSocialBlogArticles.ts +60 -0
- package/lobb/tests/utils/data/articles.ts +65 -0
- package/lobb/tests/utils/data/socialBlogArticles.ts +56 -0
- package/package.json +32 -0
- package/studio/.env +1 -0
- package/studio/README.md +1 -0
- package/studio/index.html +13 -0
- package/studio/postcss.config.js +6 -0
- package/studio/public/vite.svg +1 -0
- package/studio/src/auth.ts +57 -0
- package/studio/src/index.ts +54 -0
- package/studio/src/main.ts +12 -0
- package/studio/src/onStartup.ts +25 -0
- package/studio/src/pages/loginPage/index.svelte +64 -0
- package/studio/src/pages/settings/index.svelte +53 -0
- package/studio/src/pages/settings/pages/activityFeed.svelte +21 -0
- package/studio/src/pages/settings/pages/rolesAndPermissions.svelte +21 -0
- package/studio/src/pages/settings/pages/users.svelte +21 -0
- package/studio/src/pages/userSettings/components/account.svelte +106 -0
- package/studio/src/pages/userSettings/components/profile.svelte +87 -0
- package/studio/src/pages/userSettings/index.svelte +48 -0
- package/studio/svelte.config.js +8 -0
- package/studio/tailwind.config.ts +93 -0
- package/studio/tsconfig.app.json +22 -0
- package/studio/tsconfig.json +7 -0
- package/studio/tsconfig.node.json +26 -0
- package/studio/vite.config.ts +14 -0
- package/todo.md +37 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ExtensionUtils } from "../extension.types";
|
|
2
|
+
import { Auth } from "./auth";
|
|
3
|
+
|
|
4
|
+
export async function onStartup(utils: ExtensionUtils) {
|
|
5
|
+
// logout if we got an Unauthorized response
|
|
6
|
+
const auth = new Auth(utils);
|
|
7
|
+
utils.lobb.onResponse(async (response) => {
|
|
8
|
+
if (response.status === 401) {
|
|
9
|
+
const body = await response.json();
|
|
10
|
+
if (
|
|
11
|
+
body.message === "JWT token has expired" ||
|
|
12
|
+
body.message === "Invalid JWT token"
|
|
13
|
+
) {
|
|
14
|
+
auth.logout();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
const session = auth.getSession();
|
|
19
|
+
// if user is logged in
|
|
20
|
+
if (session) {
|
|
21
|
+
utils.ctx.extensions.auth.session = session;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
await auth.logout()
|
|
25
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ExtensionProps } from "src/extensions/extension.types";
|
|
3
|
+
import { Auth } from "../../auth";
|
|
4
|
+
|
|
5
|
+
const { utils }: ExtensionProps = $props();
|
|
6
|
+
|
|
7
|
+
let formData = {
|
|
8
|
+
email:
|
|
9
|
+
window.location.hostname === "demo.lobb.app"
|
|
10
|
+
? "admin@example.com"
|
|
11
|
+
: "",
|
|
12
|
+
password: window.location.hostname === "demo.lobb.app" ? "admin" : "",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
async function handleOnSubmit(e: Event) {
|
|
16
|
+
e.preventDefault();
|
|
17
|
+
if (!formData.email && !formData.password) {
|
|
18
|
+
utils.toast.error("Please complete all fields");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const auth = new Auth(utils);
|
|
23
|
+
await auth.login(formData);
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<div
|
|
28
|
+
class="fixed left-0 top-0 flex h-screen w-screen items-center justify-center bg-muted"
|
|
29
|
+
>
|
|
30
|
+
<div class="flex w-full max-w-[25rem] flex-col gap-6 p-6">
|
|
31
|
+
<div>
|
|
32
|
+
<div class="text-4xl">Welcome back</div>
|
|
33
|
+
<div>Sign in to your account</div>
|
|
34
|
+
</div>
|
|
35
|
+
<form
|
|
36
|
+
class="flex flex-col gap-6 rounded-md border bg-white p-6"
|
|
37
|
+
onsubmit={handleOnSubmit}
|
|
38
|
+
>
|
|
39
|
+
<div class="flex flex-col gap-2">
|
|
40
|
+
<div>
|
|
41
|
+
<div class="mb-1 text-sm font-medium">Email</div>
|
|
42
|
+
<utils.components.Input
|
|
43
|
+
bind:value={formData.email}
|
|
44
|
+
autocomplete="email"
|
|
45
|
+
placeholder="email@example.com"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
<div>
|
|
49
|
+
<div class="mb-1 text-sm font-medium">Password</div>
|
|
50
|
+
<utils.components.Input
|
|
51
|
+
bind:value={formData.password}
|
|
52
|
+
type="password"
|
|
53
|
+
autocomplete="current-password"
|
|
54
|
+
placeholder="••••••••"
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<utils.components.Button type="submit">
|
|
59
|
+
Sign In
|
|
60
|
+
</utils.components.Button>
|
|
61
|
+
<div class="text-sm underline">Forgot Password?</div>
|
|
62
|
+
</form>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ExtensionProps } from "src/extensions/extension.types";
|
|
3
|
+
import Users from "./pages/users.svelte";
|
|
4
|
+
import RolesAndPermissions from "./pages/rolesAndPermissions.svelte";
|
|
5
|
+
import ActivityFeed from "./pages/activityFeed.svelte";
|
|
6
|
+
|
|
7
|
+
const props: ExtensionProps = $props();
|
|
8
|
+
const components = props.utils.components;
|
|
9
|
+
const { Sidebar } = components;
|
|
10
|
+
const { Icons } = components;
|
|
11
|
+
const auth_settings_page = $derived(props.utils.location.url.pathname.split("/")[4]);
|
|
12
|
+
const isSmall = $derived(!props.utils.mediaQueries.sm.current);
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<!-- TODO: add the sidebar in here to add these pages
|
|
16
|
+
- users
|
|
17
|
+
- roles and permissions
|
|
18
|
+
- invitation page
|
|
19
|
+
- activity feed
|
|
20
|
+
-->
|
|
21
|
+
|
|
22
|
+
<Sidebar
|
|
23
|
+
title="Auth settings"
|
|
24
|
+
showSearch={false}
|
|
25
|
+
isSelected={(element) => {
|
|
26
|
+
const element_page = element.href?.split("/")[4];
|
|
27
|
+
return element_page === auth_settings_page;
|
|
28
|
+
}}
|
|
29
|
+
data={[
|
|
30
|
+
{
|
|
31
|
+
name: "Users",
|
|
32
|
+
href: "/extensions/auth/settings/users",
|
|
33
|
+
icon: Icons.Users
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "roles and permissions",
|
|
37
|
+
href: "/extensions/auth/settings/roles_and_permissions",
|
|
38
|
+
icon: Icons.ShieldUser
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "activity feed",
|
|
42
|
+
href: "/extensions/auth/settings/activity_feed",
|
|
43
|
+
icon: Icons.ClipboardList
|
|
44
|
+
},
|
|
45
|
+
]} >
|
|
46
|
+
{#if auth_settings_page === 'users'}
|
|
47
|
+
<Users {...props} />
|
|
48
|
+
{:else if auth_settings_page === 'roles_and_permissions'}
|
|
49
|
+
<RolesAndPermissions {...props} />
|
|
50
|
+
{:else if auth_settings_page === 'activity_feed'}
|
|
51
|
+
<ActivityFeed {...props} />
|
|
52
|
+
{/if}
|
|
53
|
+
</Sidebar>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ExtensionProps } from "src/extensions/extension.types";
|
|
3
|
+
|
|
4
|
+
const { utils }: ExtensionProps = $props();
|
|
5
|
+
const components = utils.components;
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<div class="flex flex-col gap-4 p-4 h-full">
|
|
9
|
+
<div>
|
|
10
|
+
<div class="text-xl font-bold">Activity Feed</div>
|
|
11
|
+
<div class="text-sm text-muted-foreground">
|
|
12
|
+
Monitor your users Activity Feed.
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
<components.Separator />
|
|
16
|
+
<div class="flex gap-4 border rounded-md overflow-hidden h-full bg-soft">
|
|
17
|
+
<components.DataTable
|
|
18
|
+
collectionName="auth_activity_feed"
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ExtensionProps } from "src/extensions/extension.types";
|
|
3
|
+
|
|
4
|
+
const { utils }: ExtensionProps = $props();
|
|
5
|
+
const components = utils.components;
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<div class="flex flex-col gap-4 p-4 h-full">
|
|
9
|
+
<div>
|
|
10
|
+
<div class="text-xl font-bold">Roles and Permissions</div>
|
|
11
|
+
<div class="text-sm text-muted-foreground">
|
|
12
|
+
Manage the Roles and Permissions of the system.
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
<components.Separator />
|
|
16
|
+
<div class="flex gap-4 border rounded-md overflow-hidden h-full bg-soft">
|
|
17
|
+
<components.DataTable
|
|
18
|
+
collectionName="auth_roles"
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ExtensionProps } from "src/extensions/extension.types";
|
|
3
|
+
|
|
4
|
+
const { utils }: ExtensionProps = $props();
|
|
5
|
+
const components = utils.components;
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<div class="flex flex-col gap-4 p-4 h-full">
|
|
9
|
+
<div>
|
|
10
|
+
<div class="text-xl font-bold">Users</div>
|
|
11
|
+
<div class="text-sm text-muted-foreground">
|
|
12
|
+
Manage the users of the system.
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
<components.Separator />
|
|
16
|
+
<div class="flex gap-4 border rounded-md overflow-hidden h-full bg-soft">
|
|
17
|
+
<components.DataTable
|
|
18
|
+
collectionName="auth_users"
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ExtensionProps } from "src/extensions/extension.types";
|
|
3
|
+
import { onMount } from "svelte";
|
|
4
|
+
|
|
5
|
+
const { utils }: ExtensionProps = $props();
|
|
6
|
+
|
|
7
|
+
let user = $state({
|
|
8
|
+
old_password: "",
|
|
9
|
+
new_password: "",
|
|
10
|
+
});
|
|
11
|
+
const icons = utils.components.Icons;
|
|
12
|
+
|
|
13
|
+
// @ts-ignore: Im sure the session object exist
|
|
14
|
+
const currentUserId = utils.ctx.extensions.auth.session.user.id;
|
|
15
|
+
let loaded = $state(false);
|
|
16
|
+
onMount(async () => {
|
|
17
|
+
const response = await utils.lobb.findOne("auth_users", currentUserId);
|
|
18
|
+
const result = await response.json();
|
|
19
|
+
user = result.data;
|
|
20
|
+
loaded = true;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
async function onUpdateCLick() {
|
|
24
|
+
if (!user.new_password) {
|
|
25
|
+
utils.toast.error("Make sure you fill the new password");
|
|
26
|
+
return;
|
|
27
|
+
} else if (!user.old_password) {
|
|
28
|
+
utils.toast.error("Make sure you fill the old password");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const result = await utils.lobb.request({
|
|
33
|
+
method: "POST",
|
|
34
|
+
route: "/api/extensions/auth/change_password",
|
|
35
|
+
payload: {
|
|
36
|
+
old_password: user.old_password,
|
|
37
|
+
new_password: user.new_password,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (result.status >= 400) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
utils.toast.success("Account updated successfully.");
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
{#if loaded}
|
|
50
|
+
<div class="flex max-w-[40rem] flex-1 flex-col gap-4 overflow-y-visible">
|
|
51
|
+
<div>
|
|
52
|
+
<div class="text-lg font-semibold">Account</div>
|
|
53
|
+
<div class="text-sm text-muted-foreground">
|
|
54
|
+
Update your account settings.
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
<utils.components.Separator />
|
|
58
|
+
<!-- TODO: add prefered language here too -->
|
|
59
|
+
<div class="flex flex-col gap-2">
|
|
60
|
+
<div class="text-sm">Change Password</div>
|
|
61
|
+
<div class="flex gap-4">
|
|
62
|
+
<utils.components.Input
|
|
63
|
+
bind:value={user.old_password}
|
|
64
|
+
placeholder="Old password"
|
|
65
|
+
type="password"
|
|
66
|
+
/>
|
|
67
|
+
<utils.components.Input
|
|
68
|
+
bind:value={user.new_password}
|
|
69
|
+
placeholder="New password"
|
|
70
|
+
type="password"
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="text-xs text-muted-foreground">
|
|
74
|
+
This is where you can change your password.
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
<utils.components.Button
|
|
78
|
+
class="self-start"
|
|
79
|
+
onclick={onUpdateCLick}
|
|
80
|
+
Icon={icons.Pencil}
|
|
81
|
+
>
|
|
82
|
+
Update Account
|
|
83
|
+
</utils.components.Button>
|
|
84
|
+
</div>
|
|
85
|
+
{:else}
|
|
86
|
+
<div class="flex max-w-[40rem] flex-1 flex-col gap-8 overflow-y-visible">
|
|
87
|
+
<div class="grid gap-2">
|
|
88
|
+
<utils.components.Skeleton class="h-8 w-full max-w-60" />
|
|
89
|
+
<utils.components.Skeleton class="h-4 w-full max-w-96" />
|
|
90
|
+
</div>
|
|
91
|
+
<utils.components.Separator />
|
|
92
|
+
<div class="flex flex-col gap-2">
|
|
93
|
+
<utils.components.Skeleton class="h-4 w-full max-w-36" />
|
|
94
|
+
<utils.components.Skeleton class="h-8 w-full max-w-60" />
|
|
95
|
+
<utils.components.Skeleton class="h-4 w-full max-w-96" />
|
|
96
|
+
</div>
|
|
97
|
+
<div class="flex flex-col gap-2">
|
|
98
|
+
<utils.components.Skeleton class="h-4 w-full max-w-36" />
|
|
99
|
+
<div class="flex gap-4">
|
|
100
|
+
<utils.components.Skeleton class="h-8 w-full max-w-60" />
|
|
101
|
+
<utils.components.Skeleton class="h-8 w-full max-w-60" />
|
|
102
|
+
</div>
|
|
103
|
+
<utils.components.Skeleton class="h-4 w-full max-w-96" />
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
{/if}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ExtensionProps } from "src/extensions/extension.types";
|
|
3
|
+
import { onMount } from "svelte";
|
|
4
|
+
|
|
5
|
+
const { utils }: ExtensionProps = $props();
|
|
6
|
+
|
|
7
|
+
let user = $state({
|
|
8
|
+
name: "",
|
|
9
|
+
});
|
|
10
|
+
const icons = utils.components.Icons;
|
|
11
|
+
|
|
12
|
+
// @ts-ignore: Im sure the session object exist
|
|
13
|
+
const currentUserId = utils.ctx.extensions.auth.session.user.id;
|
|
14
|
+
let loaded = $state(false);
|
|
15
|
+
onMount(async () => {
|
|
16
|
+
const response = await utils.lobb.findOne("auth_users", currentUserId);
|
|
17
|
+
const result = await response.json();
|
|
18
|
+
user = result.data;
|
|
19
|
+
loaded = true;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
async function onUpdateCLick() {
|
|
23
|
+
// TODO: add the logic of updating some of the current user's information
|
|
24
|
+
const updateObject: any = {};
|
|
25
|
+
updateObject["name"] = user.name;
|
|
26
|
+
const response = await utils.lobb.updateOne(
|
|
27
|
+
"auth_users",
|
|
28
|
+
currentUserId,
|
|
29
|
+
updateObject,
|
|
30
|
+
);
|
|
31
|
+
if (response.status >= 400) {
|
|
32
|
+
const result = await response.json();
|
|
33
|
+
utils.toast.error(result.message);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
utils.toast.success("Profile updated successfully.");
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
{#if loaded}
|
|
41
|
+
<div class="flex max-w-[40rem] flex-1 flex-col gap-4 overflow-y-visible">
|
|
42
|
+
<div>
|
|
43
|
+
<div class="text-lg font-semibold">Profile</div>
|
|
44
|
+
<div class="text-sm text-muted-foreground">
|
|
45
|
+
Edit information that others will see.
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<utils.components.Separator />
|
|
49
|
+
<!-- TODO: you should add name and other stuff like phone number and stuff like that -->
|
|
50
|
+
<div class="flex flex-col gap-2">
|
|
51
|
+
<div class="text-sm">Full Name</div>
|
|
52
|
+
<utils.components.Input
|
|
53
|
+
placeholder="Full Name"
|
|
54
|
+
bind:value={user.name}
|
|
55
|
+
/>
|
|
56
|
+
<div class="text-xs text-muted-foreground">
|
|
57
|
+
This is your public display name. It can be your real name or a
|
|
58
|
+
pseudonym.
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<utils.components.Button
|
|
62
|
+
class="self-start"
|
|
63
|
+
onclick={onUpdateCLick}
|
|
64
|
+
Icon={icons.Pencil}
|
|
65
|
+
>
|
|
66
|
+
Update Profile
|
|
67
|
+
</utils.components.Button>
|
|
68
|
+
</div>
|
|
69
|
+
{:else}
|
|
70
|
+
<div class="flex max-w-[40rem] flex-1 flex-col gap-8 overflow-y-visible">
|
|
71
|
+
<div class="grid gap-2">
|
|
72
|
+
<utils.components.Skeleton class="h-8 w-full max-w-60" />
|
|
73
|
+
<utils.components.Skeleton class="h-4 w-full max-w-96" />
|
|
74
|
+
</div>
|
|
75
|
+
<utils.components.Separator />
|
|
76
|
+
<div class="flex flex-col gap-2">
|
|
77
|
+
<utils.components.Skeleton class="h-4 w-full max-w-36" />
|
|
78
|
+
<utils.components.Skeleton class="h-8 w-full max-w-60" />
|
|
79
|
+
<utils.components.Skeleton class="h-4 w-full max-w-96" />
|
|
80
|
+
</div>
|
|
81
|
+
<div class="flex flex-col gap-2">
|
|
82
|
+
<utils.components.Skeleton class="h-4 w-full max-w-36" />
|
|
83
|
+
<utils.components.Skeleton class="h-8 w-full max-w-60" />
|
|
84
|
+
<utils.components.Skeleton class="h-4 w-full max-w-96" />
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
{/if}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ExtensionProps } from "src/extensions/extension.types";
|
|
3
|
+
import Profile from "./components/profile.svelte";
|
|
4
|
+
import Account from "./components/account.svelte";
|
|
5
|
+
|
|
6
|
+
const props: ExtensionProps = $props();
|
|
7
|
+
const components = props.utils.components;
|
|
8
|
+
const isSmall = $derived(!props.utils.mediaQueries.sm.current);
|
|
9
|
+
|
|
10
|
+
let selectedView: "profile" | "account" = $state("profile");
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div class="flex flex-col gap-4 p-4">
|
|
14
|
+
<div>
|
|
15
|
+
<div class="text-xl font-bold">User Settings</div>
|
|
16
|
+
<div class="text-sm text-muted-foreground">
|
|
17
|
+
Manage your user settings.
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
<components.Separator />
|
|
21
|
+
<div class="flex gap-4 {isSmall ? "flex-col" : ""}">
|
|
22
|
+
<div class="flex w-64 {isSmall ? "" : "flex-col"}">
|
|
23
|
+
<components.Button
|
|
24
|
+
onclick={() => (selectedView = "profile")}
|
|
25
|
+
variant="ghost"
|
|
26
|
+
class="flex justify-start text-muted-foreground hover:underline"
|
|
27
|
+
>
|
|
28
|
+
Profile
|
|
29
|
+
</components.Button>
|
|
30
|
+
<components.Button
|
|
31
|
+
onclick={() => (selectedView = "account")}
|
|
32
|
+
variant="ghost"
|
|
33
|
+
class="flex justify-start text-muted-foreground hover:underline"
|
|
34
|
+
>
|
|
35
|
+
Account
|
|
36
|
+
</components.Button>
|
|
37
|
+
</div>
|
|
38
|
+
{#if selectedView === "profile"}
|
|
39
|
+
<Profile {...props} />
|
|
40
|
+
{:else if selectedView === "account"}
|
|
41
|
+
<Account {...props} />
|
|
42
|
+
{:else}
|
|
43
|
+
<div class="font-bold text-red-500">
|
|
44
|
+
The "{selectedView}" view doesnt exist
|
|
45
|
+
</div>
|
|
46
|
+
{/if}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
|
2
|
+
|
|
3
|
+
/** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */
|
|
4
|
+
export default {
|
|
5
|
+
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
|
6
|
+
// for more information about preprocessors
|
|
7
|
+
preprocess: vitePreprocess(),
|
|
8
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { fontFamily } from "tailwindcss/defaultTheme";
|
|
2
|
+
import type { Config } from "tailwindcss";
|
|
3
|
+
import tailwindcssAnimate from "tailwindcss-animate";
|
|
4
|
+
|
|
5
|
+
const config: Config = {
|
|
6
|
+
darkMode: ["class"],
|
|
7
|
+
content: [
|
|
8
|
+
"./src/**/*.{html,js,svelte,ts}",
|
|
9
|
+
// TODO: this part should be removed .. it doesnt make sense
|
|
10
|
+
"../../../packages/studio/src/**/*.{html,js,svelte,ts}",
|
|
11
|
+
],
|
|
12
|
+
safelist: ["dark"],
|
|
13
|
+
theme: {
|
|
14
|
+
container: {
|
|
15
|
+
center: true,
|
|
16
|
+
padding: "2rem",
|
|
17
|
+
screens: {
|
|
18
|
+
"2xl": "1400px",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
extend: {
|
|
22
|
+
colors: {
|
|
23
|
+
border: "hsl(var(--border) / <alpha-value>)",
|
|
24
|
+
input: "hsl(var(--input) / <alpha-value>)",
|
|
25
|
+
ring: "hsl(var(--ring) / <alpha-value>)",
|
|
26
|
+
background: "hsl(var(--background) / <alpha-value>)",
|
|
27
|
+
foreground: "hsl(var(--foreground) / <alpha-value>)",
|
|
28
|
+
primary: {
|
|
29
|
+
DEFAULT: "hsl(var(--primary) / <alpha-value>)",
|
|
30
|
+
foreground: "hsl(var(--primary-foreground) / <alpha-value>)",
|
|
31
|
+
},
|
|
32
|
+
secondary: {
|
|
33
|
+
DEFAULT: "hsl(var(--secondary) / <alpha-value>)",
|
|
34
|
+
foreground: "hsl(var(--secondary-foreground) / <alpha-value>)",
|
|
35
|
+
},
|
|
36
|
+
destructive: {
|
|
37
|
+
DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
|
|
38
|
+
foreground: "hsl(var(--destructive-foreground) / <alpha-value>)",
|
|
39
|
+
},
|
|
40
|
+
soft: {
|
|
41
|
+
DEFAULT: "hsl(var(--soft) / <alpha-value>)",
|
|
42
|
+
},
|
|
43
|
+
muted: {
|
|
44
|
+
DEFAULT: "hsl(var(--muted) / <alpha-value>)",
|
|
45
|
+
foreground: "hsl(var(--muted-foreground) / <alpha-value>)",
|
|
46
|
+
},
|
|
47
|
+
accent: {
|
|
48
|
+
DEFAULT: "hsl(var(--accent) / <alpha-value>)",
|
|
49
|
+
foreground: "hsl(var(--accent-foreground) / <alpha-value>)",
|
|
50
|
+
},
|
|
51
|
+
popover: {
|
|
52
|
+
DEFAULT: "hsl(var(--popover) / <alpha-value>)",
|
|
53
|
+
foreground: "hsl(var(--popover-foreground) / <alpha-value>)",
|
|
54
|
+
},
|
|
55
|
+
card: {
|
|
56
|
+
DEFAULT: "hsl(var(--card) / <alpha-value>)",
|
|
57
|
+
foreground: "hsl(var(--card-foreground) / <alpha-value>)",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
borderRadius: {
|
|
61
|
+
xl: "calc(var(--radius) + 4px)",
|
|
62
|
+
lg: "var(--radius)",
|
|
63
|
+
md: "calc(var(--radius) - 2px)",
|
|
64
|
+
sm: "calc(var(--radius) - 4px)",
|
|
65
|
+
},
|
|
66
|
+
fontFamily: {
|
|
67
|
+
sans: [...fontFamily.sans],
|
|
68
|
+
},
|
|
69
|
+
keyframes: {
|
|
70
|
+
"accordion-down": {
|
|
71
|
+
from: { height: "0" },
|
|
72
|
+
to: { height: "var(--bits-accordion-content-height)" },
|
|
73
|
+
},
|
|
74
|
+
"accordion-up": {
|
|
75
|
+
from: { height: "var(--bits-accordion-content-height)" },
|
|
76
|
+
to: { height: "0" },
|
|
77
|
+
},
|
|
78
|
+
"caret-blink": {
|
|
79
|
+
"0%,70%,100%": { opacity: "1" },
|
|
80
|
+
"20%,50%": { opacity: "0" },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
animation: {
|
|
84
|
+
"accordion-down": "accordion-down 0.2s ease-out",
|
|
85
|
+
"accordion-up": "accordion-up 0.2s ease-out",
|
|
86
|
+
"caret-blink": "caret-blink 1.25s ease-out infinite",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
plugins: [tailwindcssAnimate],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export default config;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@tsconfig/svelte/tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
5
|
+
"target": "ES2022",
|
|
6
|
+
"useDefineForClassFields": true,
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"types": ["svelte", "vite/client"],
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"allowArbitraryExtensions": true,
|
|
11
|
+
/**
|
|
12
|
+
* Typecheck JS in `.svelte` and `.js` files by default.
|
|
13
|
+
* Disable checkJs if you'd like to use dynamic types in JS.
|
|
14
|
+
* Note that setting allowJs false does not prevent the use
|
|
15
|
+
* of JS in `.svelte` files.
|
|
16
|
+
*/
|
|
17
|
+
"allowJs": true,
|
|
18
|
+
"checkJs": true,
|
|
19
|
+
"moduleDetection": "force"
|
|
20
|
+
},
|
|
21
|
+
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
|
|
22
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "ES2023",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
/* Linting */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"erasableSyntaxOnly": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedSideEffectImports": true
|
|
24
|
+
},
|
|
25
|
+
"include": ["vite.config.ts"]
|
|
26
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
// https://vite.dev/config/
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [svelte()],
|
|
8
|
+
resolve: {
|
|
9
|
+
alias: {
|
|
10
|
+
// TODO: remove this. this prevents this app from being portable
|
|
11
|
+
$lib: path.resolve("../../../packages/studio/src/lib"),
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|
package/todo.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# high priority
|
|
2
|
+
|
|
3
|
+
- remove the `dashboard_access_roles` and just put it in the specific role in the `roles` property
|
|
4
|
+
- currently we have the payloadGuard. you can later implement the `recordGuard`
|
|
5
|
+
for `update` and `delete`
|
|
6
|
+
- implement rotating refresh token mechanism
|
|
7
|
+
- you can implement the firebase adapter and get all other adapters
|
|
8
|
+
- check `https://passkeys.dev/`. the new standart to abandon passwords
|
|
9
|
+
- prevent any one from creating a user with the admin role
|
|
10
|
+
|
|
11
|
+
# low priority
|
|
12
|
+
|
|
13
|
+
- add basic authentication in lobb. or at least make something like it. where
|
|
14
|
+
you have a users.json file and those will be the users without creating a
|
|
15
|
+
users or anything like that. its much simpler for simpler usage
|
|
16
|
+
- the collection_name property in the refresh_token property default value in
|
|
17
|
+
the schema doent work. its similar to the issue of the auth collection name.
|
|
18
|
+
although Im adding a default value to that property. but its value is
|
|
19
|
+
undefined for some reason. investigate it
|
|
20
|
+
- remove keep only the user id in the payload of the access and refresh tokens
|
|
21
|
+
- rotational refresh tokens where the refresh token is changes every time its
|
|
22
|
+
used to refresh an access token
|
|
23
|
+
- prepare the views directory for extensions in a better way. now there is a way
|
|
24
|
+
to view all component you have in an isolated manner. and when you run
|
|
25
|
+
`npm run build` it will generate a js compiled version of the svelte
|
|
26
|
+
component.
|
|
27
|
+
- for now you need to import the main css file in all of the components in order
|
|
28
|
+
for tailwind stuff to be indluded in the main style.css. but in the future you
|
|
29
|
+
should import all component in an index file and include the main css there
|
|
30
|
+
and force vite to create seperate chunks for each imported svelte compoenent
|
|
31
|
+
there. so that you dont get one bundle because you need them seperated
|
|
32
|
+
- change the views from svelte files into directories that holds index.svelte
|
|
33
|
+
files. (this is better because maybe in the future you want to modularize or
|
|
34
|
+
split your code or have other files. so they will all be organized inside that
|
|
35
|
+
directory and you dont need to have a svelte file and then have a directory
|
|
36
|
+
with the same name of that file to hold all the imports of that svelte file.
|
|
37
|
+
its better to have them all stored in one directory)
|