@lobb-js/lobb-ext-auth 0.10.4 → 0.11.1
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/README.md +1 -0
- package/dist/auth.d.ts +2 -1
- package/dist/auth.js +23 -4
- package/dist/index.js +41 -2
- package/dist/lib/components/pages/settings/index.svelte +1 -1
- package/dist/lib/components/pages/settings/pages/activityFeed.svelte +1 -1
- package/dist/lib/components/pages/settings/pages/rolesAndPermissions.svelte +1 -1
- package/dist/lib/components/pages/settings/pages/users.svelte +1 -1
- package/dist/lib/components/pages/userSettings/index.svelte +45 -32
- package/dist/onStartup.js +17 -2
- package/extensions/auth/collections/collections.ts +2 -0
- package/extensions/auth/collections/shares.ts +60 -0
- package/extensions/auth/config/extensionConfigSchema.d.ts +41 -0
- package/extensions/auth/config/permissionsAction/create.d.ts +18 -0
- package/extensions/auth/config/permissionsAction/delete.d.ts +3 -0
- package/extensions/auth/config/permissionsAction/read.d.ts +11 -0
- package/extensions/auth/config/permissionsAction/update.d.ts +18 -0
- package/extensions/auth/index.ts +0 -2
- package/extensions/auth/permissions.d.ts +2 -0
- package/extensions/auth/permissions.ts +34 -0
- package/extensions/auth/studio/auth.ts +25 -5
- package/extensions/auth/studio/index.ts +44 -2
- package/extensions/auth/studio/lib/components/pages/settings/index.svelte +1 -1
- package/extensions/auth/studio/lib/components/pages/settings/pages/activityFeed.svelte +1 -1
- package/extensions/auth/studio/lib/components/pages/settings/pages/rolesAndPermissions.svelte +1 -1
- package/extensions/auth/studio/lib/components/pages/settings/pages/users.svelte +1 -1
- package/extensions/auth/studio/lib/components/pages/userSettings/index.svelte +45 -32
- package/extensions/auth/studio/onStartup.ts +14 -2
- package/extensions/auth/tests/collections/shares.test.ts +657 -0
- package/extensions/auth/tests/configs/auth.ts +17 -0
- package/extensions/auth/tests/controllers/me.test.ts +104 -0
- package/extensions/auth/tests/permissions.test.ts +127 -0
- package/extensions/auth/tests/workflows/shareIntersection.test.ts +158 -0
- package/extensions/auth/workflows/baseWorkflow.ts +48 -26
- package/extensions/auth/workflows/currentUserPermissionsWorkflow.ts +32 -0
- package/extensions/auth/workflows/index.ts +12 -0
- package/extensions/auth/workflows/meAliasWorkflows.ts +26 -0
- package/extensions/auth/workflows/policiesWorkflows.ts +64 -117
- package/extensions/auth/workflows/shareIntersection.ts +64 -0
- package/extensions/auth/workflows/sharesWorkflows.ts +135 -0
- package/extensions/auth/workflows/utils.ts +132 -224
- package/package.json +4 -6
- package/dist/lib/components/pages/userSettings/components/account.svelte +0 -106
- package/dist/lib/components/pages/userSettings/components/account.svelte.d.ts +0 -14
- package/dist/lib/components/pages/userSettings/components/profile.svelte +0 -87
- package/dist/lib/components/pages/userSettings/components/profile.svelte.d.ts +0 -14
- package/dist/tests/login.spec.d.ts +0 -1
- package/dist/tests/login.spec.js +0 -27
- package/dist/tests/package.json +0 -1
- package/dist/tests/playwright.config.cjs +0 -27
- package/dist/tests/playwright.config.d.cts +0 -2
- package/extensions/auth/studio/lib/components/pages/userSettings/components/account.svelte +0 -106
- package/extensions/auth/studio/lib/components/pages/userSettings/components/profile.svelte +0 -87
- package/extensions/auth/studio/tests/login.spec.ts +0 -34
- package/extensions/auth/studio/tests/package.json +0 -1
- package/extensions/auth/studio/tests/playwright.config.cjs +0 -27
|
@@ -1,286 +1,194 @@
|
|
|
1
1
|
import type { EventContext, Filter, Lobb } from "@lobb-js/core";
|
|
2
|
+
import type { Context } from "hono";
|
|
2
3
|
import type {
|
|
3
4
|
CollectionPermissionActionsKeys,
|
|
4
5
|
ExtensionConfig,
|
|
6
|
+
PermissionsConfig,
|
|
5
7
|
User,
|
|
6
8
|
} from "../config/extensionConfigSchema.ts";
|
|
7
|
-
import {
|
|
9
|
+
import { isActionAllowed } from "../permissions.ts";
|
|
10
|
+
|
|
11
|
+
interface HandlePolicyOutput {
|
|
12
|
+
filter?: Filter;
|
|
13
|
+
payload?: Record<string, unknown>;
|
|
14
|
+
}
|
|
8
15
|
|
|
9
16
|
interface HandlePolicyProps {
|
|
10
17
|
ctx: EventContext;
|
|
11
18
|
collectionName: string;
|
|
12
19
|
action: CollectionPermissionActionsKeys;
|
|
13
|
-
|
|
14
|
-
input: any | undefined;
|
|
15
|
-
role?: string;
|
|
20
|
+
permissions: PermissionsConfig | undefined;
|
|
16
21
|
lobb: Lobb;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
// Used by both filter-template rendering (for reads) and as the `user`
|
|
23
|
+
// argument passed into guard / mutate callbacks (for writes).
|
|
24
|
+
user?: User | undefined;
|
|
25
|
+
// Payload for create/update — passed to fields allowlist, guard, mutate.
|
|
21
26
|
payload?: Record<string, unknown>;
|
|
22
|
-
|
|
27
|
+
// Target id for findOne/update/delete — used by the self-read bypass.
|
|
28
|
+
recordId?: string | number;
|
|
29
|
+
// Query filter for findAll — used by the self-read bypass.
|
|
30
|
+
filter?: Record<string, unknown>;
|
|
23
31
|
}
|
|
24
32
|
|
|
33
|
+
// Permission decision for a request, regardless of where the permissions
|
|
34
|
+
// came from (role config or share's embedded snapshot). Walks the tree,
|
|
35
|
+
// throws FORBIDDEN if denied, returns void when unconditionally allowed,
|
|
36
|
+
// returns { filter? } for conditional reads, returns { payload? } when
|
|
37
|
+
// create/update mutators were applied. Static features (filter, fields
|
|
38
|
+
// allowlist) work for both roles and shares; dynamic features (payload
|
|
39
|
+
// guard, mutators) only fire when their function values exist in the
|
|
40
|
+
// permissions object — for shares they're absent (stripped by JSON
|
|
41
|
+
// serialisation) so those branches naturally no-op.
|
|
25
42
|
export function handlePolicy({
|
|
26
43
|
ctx,
|
|
27
44
|
collectionName,
|
|
28
45
|
action,
|
|
29
|
-
|
|
30
|
-
input,
|
|
46
|
+
permissions,
|
|
31
47
|
lobb,
|
|
32
|
-
|
|
33
|
-
|
|
48
|
+
user,
|
|
49
|
+
payload,
|
|
50
|
+
recordId,
|
|
51
|
+
filter,
|
|
34
52
|
}: HandlePolicyProps): HandlePolicyOutput | void {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
code: "FORBIDDEN",
|
|
46
|
-
message: "You do not have permission to perform this action.",
|
|
47
|
-
});
|
|
53
|
+
// Self-row bypass: an authenticated user can always read/update/delete
|
|
54
|
+
// their own auth_users row regardless of role. Share requests have
|
|
55
|
+
// `user === undefined`, so they never hit this branch — they only get
|
|
56
|
+
// what their embedded permissions explicitly grant.
|
|
57
|
+
if (user && collectionName === "auth_users") {
|
|
58
|
+
if (action === "read" && filter && Object.keys(filter).length === 1 && filter.id === user.id) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if ((action === "update" || action === "delete") && recordId !== undefined && recordId === user.id) {
|
|
62
|
+
return;
|
|
48
63
|
}
|
|
49
|
-
throw new ctx.LobbError({
|
|
50
|
-
code: "FORBIDDEN",
|
|
51
|
-
message: "Your role is not recognized by the system.",
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const currentRolePermission = currentRole.permissions;
|
|
56
|
-
|
|
57
|
-
if (currentRolePermission === true) {
|
|
58
|
-
return;
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (typeof collectionPermission === "undefined") {
|
|
66
|
+
if (!isActionAllowed(collectionName, action, permissions)) {
|
|
64
67
|
throw new ctx.LobbError({
|
|
65
68
|
code: "FORBIDDEN",
|
|
66
69
|
message: "You do not have permission to perform this action.",
|
|
67
70
|
});
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const permissionAction = collectionPermission[action];
|
|
73
|
+
const collPerm = permissions === true ? true : permissions![collectionName];
|
|
74
|
+
const actionPerm = collPerm === true ? true : collPerm![action];
|
|
75
75
|
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (typeof permissionAction === "boolean") {
|
|
84
|
-
if (permissionAction === false) {
|
|
85
|
-
throw new ctx.LobbError({
|
|
86
|
-
code: "FORBIDDEN",
|
|
87
|
-
message: "You do not have permission to perform this action.",
|
|
88
|
-
});
|
|
76
|
+
if (action === "read") {
|
|
77
|
+
if (
|
|
78
|
+
typeof actionPerm === "object" && actionPerm !== null && actionPerm.filter
|
|
79
|
+
) {
|
|
80
|
+
return {
|
|
81
|
+
filter: lobb.utils.renderTemplateDeep(actionPerm.filter, { user }),
|
|
82
|
+
};
|
|
89
83
|
}
|
|
90
|
-
|
|
91
84
|
return;
|
|
92
85
|
}
|
|
93
86
|
|
|
94
|
-
if (action === "create") {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
input.data,
|
|
107
|
-
user,
|
|
108
|
-
extensionConfig,
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
payload: data,
|
|
113
|
-
};
|
|
114
|
-
} else if (action === "read") {
|
|
115
|
-
const readFilter = getFilter(user?.role, input.collectionName, "read", extensionConfig);
|
|
116
|
-
const filter = lobb.utils.renderTemplateDeep(
|
|
117
|
-
readFilter,
|
|
118
|
-
{
|
|
119
|
-
user,
|
|
120
|
-
},
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
filter,
|
|
125
|
-
};
|
|
126
|
-
} else if (action === "update") {
|
|
127
|
-
runGuard(user?.role, input.collectionName, "update", {
|
|
128
|
-
payload: input.data,
|
|
129
|
-
user: user,
|
|
130
|
-
}, extensionConfig);
|
|
131
|
-
|
|
132
|
-
handleFields(user?.role, input.collectionName, "update", input.data, extensionConfig);
|
|
133
|
-
|
|
134
|
-
const data = handleMutate(
|
|
135
|
-
user?.role,
|
|
136
|
-
input.collectionName,
|
|
137
|
-
"update",
|
|
138
|
-
input.data,
|
|
139
|
-
user,
|
|
140
|
-
extensionConfig,
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
payload: data,
|
|
145
|
-
};
|
|
146
|
-
} else if (action === "delete") {
|
|
147
|
-
} else {
|
|
148
|
-
throw new Error(
|
|
149
|
-
`The (${action}) action is not implemented in the (auth) extension (handlePolicy) function`,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function getFilter(
|
|
155
|
-
role: string = "public",
|
|
156
|
-
collectionName: string,
|
|
157
|
-
action: "read",
|
|
158
|
-
extensionConfig: ExtensionConfig,
|
|
159
|
-
) {
|
|
160
|
-
const config = extensionConfig;
|
|
161
|
-
const permissions = config.roles[role]?.permissions;
|
|
162
|
-
|
|
163
|
-
if (permissions && permissions !== true) {
|
|
164
|
-
const collectionPermission = permissions[collectionName];
|
|
165
|
-
if (collectionPermission && collectionPermission !== true) {
|
|
166
|
-
const permission = collectionPermission[action];
|
|
167
|
-
if (permission && permission !== true) {
|
|
168
|
-
return permission.filter;
|
|
87
|
+
if (action === "create" || action === "update") {
|
|
88
|
+
// Field allowlist — static, works for both roles and shares.
|
|
89
|
+
if (actionPerm.fields && payload) {
|
|
90
|
+
const allowedFields = Object.keys(actionPerm.fields);
|
|
91
|
+
for (const fieldName of Object.keys(payload)) {
|
|
92
|
+
if (!allowedFields.includes(fieldName)) {
|
|
93
|
+
throw new ctx.LobbError({
|
|
94
|
+
code: "FORBIDDEN",
|
|
95
|
+
message:
|
|
96
|
+
`You do not have permission to modify the field (${fieldName}).`,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
169
99
|
}
|
|
170
100
|
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
101
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const permissions = config.roles[role]?.permissions;
|
|
183
|
-
|
|
184
|
-
if (permissions && permissions !== true) {
|
|
185
|
-
const collectionPermission = permissions[collectionName];
|
|
186
|
-
if (collectionPermission && collectionPermission !== true) {
|
|
187
|
-
const permission = collectionPermission[action];
|
|
188
|
-
if (permission && permission !== true) {
|
|
189
|
-
if (permission.payloadGuard) {
|
|
190
|
-
const result = permission.payloadGuard(context);
|
|
191
|
-
if (!result) {
|
|
192
|
-
throw new LobbError({
|
|
193
|
-
code: "FORBIDDEN",
|
|
194
|
-
message: "You do not have permission to perform this action.",
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
}
|
|
102
|
+
// Payload guard — dynamic function; absent for shares.
|
|
103
|
+
if (actionPerm.payloadGuard && payload) {
|
|
104
|
+
const allowed = actionPerm.payloadGuard({ payload, user });
|
|
105
|
+
if (!allowed) {
|
|
106
|
+
throw new ctx.LobbError({
|
|
107
|
+
code: "FORBIDDEN",
|
|
108
|
+
message: "You do not have permission to perform this action.",
|
|
109
|
+
});
|
|
198
110
|
}
|
|
199
111
|
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
112
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const fields = permission.fields;
|
|
220
|
-
const allowedFields = Object.keys(fields);
|
|
221
|
-
const existingFields = Object.keys(payload);
|
|
222
|
-
// add the handler logic of the fields
|
|
223
|
-
for (const fieldName of existingFields) {
|
|
224
|
-
if (!allowedFields.includes(fieldName)) {
|
|
225
|
-
throw new LobbError({
|
|
226
|
-
code: "FORBIDDEN",
|
|
227
|
-
message:
|
|
228
|
-
`You do not have permission to modify the field (${fieldName}).`,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
113
|
+
// Mutators — dynamic functions; absent for shares.
|
|
114
|
+
if (actionPerm.mutate && payload) {
|
|
115
|
+
const mutated: Record<string, unknown> = structuredClone(payload);
|
|
116
|
+
for (
|
|
117
|
+
const [fieldName, mutateFn] of Object.entries(actionPerm.mutate) as [
|
|
118
|
+
string,
|
|
119
|
+
(
|
|
120
|
+
ctx: { value: unknown; payload: Record<string, unknown>; user?: User },
|
|
121
|
+
) => unknown,
|
|
122
|
+
][]
|
|
123
|
+
) {
|
|
124
|
+
mutated[fieldName] = mutateFn({
|
|
125
|
+
value: payload[fieldName],
|
|
126
|
+
payload,
|
|
127
|
+
user,
|
|
128
|
+
});
|
|
233
129
|
}
|
|
130
|
+
return { payload: mutated };
|
|
234
131
|
}
|
|
235
132
|
}
|
|
133
|
+
|
|
134
|
+
// delete and other actions with conditional perms have nothing extra to apply.
|
|
236
135
|
}
|
|
237
136
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
137
|
+
interface ResolvedPermissions {
|
|
138
|
+
permissions: PermissionsConfig | undefined;
|
|
139
|
+
user: User | undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Picks the permission set for a request based on its bearer credential:
|
|
143
|
+
// share token → share's embedded permissions; otherwise the user's role
|
|
144
|
+
// in the extension config. Admin gets `permissions: true` which downstream
|
|
145
|
+
// (handlePolicy, handleReadFields) treats as unconditional pass-through.
|
|
146
|
+
// Throws FORBIDDEN when the role is unknown.
|
|
147
|
+
export function resolveRequestPermissions(
|
|
148
|
+
context: Context,
|
|
244
149
|
extensionConfig: ExtensionConfig,
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
150
|
+
ctx: EventContext,
|
|
151
|
+
): ResolvedPermissions {
|
|
152
|
+
const share = context.get("auth_share") as
|
|
153
|
+
| { permissions: PermissionsConfig }
|
|
154
|
+
| undefined;
|
|
155
|
+
if (share) {
|
|
156
|
+
return { permissions: share.permissions, user: undefined };
|
|
157
|
+
}
|
|
249
158
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
159
|
+
const user = context.get("auth_user") as User | undefined;
|
|
160
|
+
const role = user?.role ?? "public";
|
|
161
|
+
|
|
162
|
+
if (role === "admin") {
|
|
163
|
+
return { permissions: true, user };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const currentRole = extensionConfig.roles?.[role];
|
|
167
|
+
if (typeof currentRole === "undefined") {
|
|
168
|
+
if (role === "public") {
|
|
169
|
+
throw new ctx.LobbError({
|
|
170
|
+
code: "FORBIDDEN",
|
|
171
|
+
message: "You do not have permission to perform this action.",
|
|
172
|
+
});
|
|
267
173
|
}
|
|
174
|
+
throw new ctx.LobbError({
|
|
175
|
+
code: "FORBIDDEN",
|
|
176
|
+
message: "Your role is not recognized by the system.",
|
|
177
|
+
});
|
|
268
178
|
}
|
|
269
179
|
|
|
270
|
-
return
|
|
180
|
+
return { permissions: currentRole.permissions, user };
|
|
271
181
|
}
|
|
272
182
|
|
|
183
|
+
// Post-read field allowlist. Filters out fields not in the role's
|
|
184
|
+
// `read.fields` allowlist. Static feature — works for both roles and shares.
|
|
273
185
|
export function handleReadFields(
|
|
274
|
-
|
|
186
|
+
permissions: PermissionsConfig | undefined,
|
|
275
187
|
collectionName: string,
|
|
276
188
|
payload: Record<string, unknown> | Record<string, unknown>[],
|
|
277
|
-
extensionConfig: ExtensionConfig,
|
|
278
189
|
) {
|
|
279
|
-
// filter the payload based on fields
|
|
280
|
-
const config = extensionConfig;
|
|
281
190
|
let clonedPayload = structuredClone(payload);
|
|
282
191
|
|
|
283
|
-
const permissions = config.roles[role]?.permissions;
|
|
284
192
|
if (permissions && typeof permissions !== "boolean") {
|
|
285
193
|
const collectionPermission = permissions[collectionName];
|
|
286
194
|
if (collectionPermission && typeof collectionPermission !== "boolean") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobb-js/lobb-ext-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -18,9 +18,8 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
|
-
"test": "bun
|
|
21
|
+
"test": "bun test extensions/auth/tests",
|
|
22
22
|
"test:lobb": "bun test extensions/auth/tests",
|
|
23
|
-
"test:studio": "bun --bun playwright test --config extensions/auth/studio/tests/playwright.config.cjs",
|
|
24
23
|
"dev": "bun run --watch lobb.ts",
|
|
25
24
|
"dev:studio": "vite dev",
|
|
26
25
|
"build": "vite build",
|
|
@@ -33,14 +32,13 @@
|
|
|
33
32
|
"package": "svelte-package --input extensions/auth/studio"
|
|
34
33
|
},
|
|
35
34
|
"dependencies": {
|
|
36
|
-
"@lobb-js/core": "^0.
|
|
35
|
+
"@lobb-js/core": "^0.32.1",
|
|
37
36
|
"argon2": "^0.40.3",
|
|
38
37
|
"hono": "^4.7.0"
|
|
39
38
|
},
|
|
40
39
|
"devDependencies": {
|
|
41
|
-
"@lobb-js/studio": "^0.
|
|
40
|
+
"@lobb-js/studio": "^0.29.1",
|
|
42
41
|
"@lucide/svelte": "^0.563.1",
|
|
43
|
-
"@playwright/test": "^1.58.2",
|
|
44
42
|
"@sveltejs/adapter-node": "^5.5.4",
|
|
45
43
|
"@sveltejs/kit": "^2.60.1",
|
|
46
44
|
"@sveltejs/package": "^2.5.7",
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { ExtensionProps } from "@lobb-js/studio";
|
|
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}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
declare const __propDef: {
|
|
3
|
-
props: Record<string, never>;
|
|
4
|
-
events: {
|
|
5
|
-
[evt: string]: CustomEvent<any>;
|
|
6
|
-
};
|
|
7
|
-
slots: {};
|
|
8
|
-
};
|
|
9
|
-
export type AccountProps = typeof __propDef.props;
|
|
10
|
-
export type AccountEvents = typeof __propDef.events;
|
|
11
|
-
export type AccountSlots = typeof __propDef.slots;
|
|
12
|
-
export default class Account extends SvelteComponentTyped<AccountProps, AccountEvents, AccountSlots> {
|
|
13
|
-
}
|
|
14
|
-
export {};
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { ExtensionProps } from "@lobb-js/studio";
|
|
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}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
declare const __propDef: {
|
|
3
|
-
props: Record<string, never>;
|
|
4
|
-
events: {
|
|
5
|
-
[evt: string]: CustomEvent<any>;
|
|
6
|
-
};
|
|
7
|
-
slots: {};
|
|
8
|
-
};
|
|
9
|
-
export type ProfileProps = typeof __propDef.props;
|
|
10
|
-
export type ProfileEvents = typeof __propDef.events;
|
|
11
|
-
export type ProfileSlots = typeof __propDef.slots;
|
|
12
|
-
export default class Profile extends SvelteComponentTyped<ProfileProps, ProfileEvents, ProfileSlots> {
|
|
13
|
-
}
|
|
14
|
-
export {};
|