or3-provider-basic-auth 0.0.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 +99 -0
- package/dist/module.d.mts +5 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +42 -0
- package/dist/runtime/components/BasicAuthChangePasswordModal.client.d.vue.ts +12 -0
- package/dist/runtime/components/BasicAuthChangePasswordModal.client.vue +133 -0
- package/dist/runtime/components/BasicAuthChangePasswordModal.client.vue.d.ts +12 -0
- package/dist/runtime/components/BasicAuthRegisterModal.client.d.vue.ts +11 -0
- package/dist/runtime/components/BasicAuthRegisterModal.client.vue +164 -0
- package/dist/runtime/components/BasicAuthRegisterModal.client.vue.d.ts +11 -0
- package/dist/runtime/components/BasicAuthSignInModal.client.d.vue.ts +13 -0
- package/dist/runtime/components/BasicAuthSignInModal.client.vue +136 -0
- package/dist/runtime/components/BasicAuthSignInModal.client.vue.d.ts +13 -0
- package/dist/runtime/components/BasicAuthUserMenu.client.d.vue.ts +12 -0
- package/dist/runtime/components/BasicAuthUserMenu.client.vue +106 -0
- package/dist/runtime/components/BasicAuthUserMenu.client.vue.d.ts +12 -0
- package/dist/runtime/components/SidebarAuthButtonBasic.client.d.vue.ts +2 -0
- package/dist/runtime/components/SidebarAuthButtonBasic.client.vue +154 -0
- package/dist/runtime/components/SidebarAuthButtonBasic.client.vue.d.ts +2 -0
- package/dist/runtime/lib/constants.d.ts +7 -0
- package/dist/runtime/lib/constants.js +7 -0
- package/dist/runtime/plugins/auth-status.client.d.ts +2 -0
- package/dist/runtime/plugins/auth-status.client.js +38 -0
- package/dist/runtime/plugins/basic-auth-ui.client.d.ts +2 -0
- package/dist/runtime/plugins/basic-auth-ui.client.js +46 -0
- package/dist/runtime/server/admin/adapters/auth-basic-auth.d.ts +2 -0
- package/dist/runtime/server/admin/adapters/auth-basic-auth.js +34 -0
- package/dist/runtime/server/api/basic-auth/_helpers.d.ts +6 -0
- package/dist/runtime/server/api/basic-auth/_helpers.js +26 -0
- package/dist/runtime/server/api/basic-auth/change-password.post.d.ts +8 -0
- package/dist/runtime/server/api/basic-auth/change-password.post.js +49 -0
- package/dist/runtime/server/api/basic-auth/refresh.post.d.ts +8 -0
- package/dist/runtime/server/api/basic-auth/refresh.post.js +78 -0
- package/dist/runtime/server/api/basic-auth/register.post.d.ts +8 -0
- package/dist/runtime/server/api/basic-auth/register.post.js +112 -0
- package/dist/runtime/server/api/basic-auth/sign-in.post.d.ts +8 -0
- package/dist/runtime/server/api/basic-auth/sign-in.post.js +75 -0
- package/dist/runtime/server/api/basic-auth/sign-out.post.d.ts +8 -0
- package/dist/runtime/server/api/basic-auth/sign-out.post.js +37 -0
- package/dist/runtime/server/auth/basic-auth-provider.d.ts +2 -0
- package/dist/runtime/server/auth/basic-auth-provider.js +41 -0
- package/dist/runtime/server/auth/index.d.ts +1 -0
- package/dist/runtime/server/auth/index.js +1 -0
- package/dist/runtime/server/db/client.d.ts +3 -0
- package/dist/runtime/server/db/client.js +106 -0
- package/dist/runtime/server/db/schema.d.ts +21 -0
- package/dist/runtime/server/db/schema.js +0 -0
- package/dist/runtime/server/lib/config.d.ts +22 -0
- package/dist/runtime/server/lib/config.js +94 -0
- package/dist/runtime/server/lib/cookies.d.ts +4 -0
- package/dist/runtime/server/lib/cookies.js +42 -0
- package/dist/runtime/server/lib/errors.d.ts +4 -0
- package/dist/runtime/server/lib/errors.js +25 -0
- package/dist/runtime/server/lib/jwt.d.ts +37 -0
- package/dist/runtime/server/lib/jwt.js +77 -0
- package/dist/runtime/server/lib/password.d.ts +2 -0
- package/dist/runtime/server/lib/password.js +8 -0
- package/dist/runtime/server/lib/rate-limit.d.ts +6 -0
- package/dist/runtime/server/lib/rate-limit.js +163 -0
- package/dist/runtime/server/lib/request-identity.d.ts +16 -0
- package/dist/runtime/server/lib/request-identity.js +32 -0
- package/dist/runtime/server/lib/request-security.d.ts +2 -0
- package/dist/runtime/server/lib/request-security.js +37 -0
- package/dist/runtime/server/lib/session-store.d.ts +46 -0
- package/dist/runtime/server/lib/session-store.js +190 -0
- package/dist/runtime/server/plugins/register.d.ts +2 -0
- package/dist/runtime/server/plugins/register.js +63 -0
- package/dist/runtime/server/token-broker/basic-auth-token-broker.d.ts +6 -0
- package/dist/runtime/server/token-broker/basic-auth-token-broker.js +8 -0
- package/dist/runtime/server/token-broker/index.d.ts +1 -0
- package/dist/runtime/server/token-broker/index.js +1 -0
- package/dist/types.d.mts +7 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# or3-provider-basic-auth
|
|
2
|
+
|
|
3
|
+
Basic-auth provider for OR3 Chat SSR mode.
|
|
4
|
+
|
|
5
|
+
This package registers:
|
|
6
|
+
|
|
7
|
+
- `AuthProvider` (`basic-auth`)
|
|
8
|
+
- `ProviderTokenBroker` (`basic-auth`)
|
|
9
|
+
- provider-owned auth UI adapter + components
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bun add or3-provider-basic-auth
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
For local development from the OR3 monorepo:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# from /Users/brendon/Documents/or3/or3-chat
|
|
21
|
+
bun add or3-provider-basic-auth@link:../or3-provider-basic-auth
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Runtime registration
|
|
25
|
+
|
|
26
|
+
Add the module to your generated provider list:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
export const or3ProviderModules = [
|
|
30
|
+
'or3-provider-basic-auth/nuxt'
|
|
31
|
+
] as const;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Environment
|
|
35
|
+
|
|
36
|
+
Required:
|
|
37
|
+
|
|
38
|
+
- `AUTH_PROVIDER=basic-auth`
|
|
39
|
+
- `OR3_BASIC_AUTH_JWT_SECRET`
|
|
40
|
+
|
|
41
|
+
Optional:
|
|
42
|
+
|
|
43
|
+
- `OR3_BASIC_AUTH_REFRESH_SECRET` (falls back to `OR3_BASIC_AUTH_JWT_SECRET`)
|
|
44
|
+
- `OR3_BASIC_AUTH_ACCESS_TTL_SECONDS` (default `900`)
|
|
45
|
+
- `OR3_BASIC_AUTH_REFRESH_TTL_SECONDS` (default `2592000`)
|
|
46
|
+
- `OR3_BASIC_AUTH_DB_PATH` (default `./.data/or3-basic-auth.sqlite`)
|
|
47
|
+
- `OR3_BASIC_AUTH_BOOTSTRAP_EMAIL`
|
|
48
|
+
- `OR3_BASIC_AUTH_BOOTSTRAP_PASSWORD`
|
|
49
|
+
|
|
50
|
+
Strict-mode behavior:
|
|
51
|
+
|
|
52
|
+
- `NODE_ENV=production` or `OR3_STRICT_CONFIG=true` fails startup if required secrets are missing.
|
|
53
|
+
- non-strict mode logs diagnostics and leaves provider registration disabled.
|
|
54
|
+
|
|
55
|
+
## Architecture notes
|
|
56
|
+
|
|
57
|
+
- Credentials/session state is stored in a provider-owned SQLite DB.
|
|
58
|
+
- Canonical OR3 users/workspaces are still resolved by the selected `AuthWorkspaceStore`.
|
|
59
|
+
- Access JWTs are short-lived and validated by `basicAuthProvider.getSession(event)`.
|
|
60
|
+
- Refresh tokens are rotated and hashed at rest; replay attempts revoke active sessions.
|
|
61
|
+
|
|
62
|
+
## Troubleshooting
|
|
63
|
+
|
|
64
|
+
- `Authentication provider is not configured`
|
|
65
|
+
- Ensure `AUTH_PROVIDER=basic-auth` and `SSR_AUTH_ENABLED=true`.
|
|
66
|
+
- Ensure `OR3_BASIC_AUTH_JWT_SECRET` is set.
|
|
67
|
+
|
|
68
|
+
- `Missing OR3_BASIC_AUTH_JWT_SECRET`
|
|
69
|
+
- In strict mode (`NODE_ENV=production` or `OR3_STRICT_CONFIG=true`), startup fails intentionally.
|
|
70
|
+
- Add the missing secret and restart.
|
|
71
|
+
|
|
72
|
+
- `Invalid credentials` on known user
|
|
73
|
+
- Verify the bootstrap account values.
|
|
74
|
+
- Confirm password updates were propagated after recent `change-password` calls.
|
|
75
|
+
|
|
76
|
+
- `Session expired` during refresh
|
|
77
|
+
- Refresh token may be revoked/rotated/replayed.
|
|
78
|
+
- Sign in again to create a new session chain.
|
|
79
|
+
|
|
80
|
+
## Intern quick start
|
|
81
|
+
|
|
82
|
+
Implementation order used in this package:
|
|
83
|
+
|
|
84
|
+
1. `src/runtime/server/lib/password.ts`
|
|
85
|
+
2. `src/runtime/server/lib/jwt.ts`
|
|
86
|
+
3. `src/runtime/server/db/client.ts` and `src/runtime/server/lib/session-store.ts`
|
|
87
|
+
4. `src/runtime/server/api/basic-auth/*.post.ts`
|
|
88
|
+
5. `src/runtime/server/auth/basic-auth-provider.ts`
|
|
89
|
+
6. `src/runtime/server/plugins/register.ts`
|
|
90
|
+
7. `src/runtime/components/*.client.vue` and `src/runtime/plugins/basic-auth-ui.client.ts`
|
|
91
|
+
8. `src/runtime/**/__tests__/*.test.ts`
|
|
92
|
+
|
|
93
|
+
## Scripts
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
bun run type-check
|
|
97
|
+
bun run test
|
|
98
|
+
bun run build
|
|
99
|
+
```
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { defineNuxtModule, createResolver, addServerPlugin, addServerHandler, addPlugin } from '@nuxt/kit';
|
|
2
|
+
|
|
3
|
+
const module$1 = defineNuxtModule({
|
|
4
|
+
meta: { name: "or3-provider-basic-auth" },
|
|
5
|
+
setup() {
|
|
6
|
+
const { resolve } = createResolver(import.meta.url);
|
|
7
|
+
addServerPlugin(resolve("runtime/server/plugins/register"));
|
|
8
|
+
addServerHandler({
|
|
9
|
+
route: "/api/basic-auth/sign-in",
|
|
10
|
+
method: "post",
|
|
11
|
+
handler: resolve("runtime/server/api/basic-auth/sign-in.post")
|
|
12
|
+
});
|
|
13
|
+
addServerHandler({
|
|
14
|
+
route: "/api/basic-auth/register",
|
|
15
|
+
method: "post",
|
|
16
|
+
handler: resolve("runtime/server/api/basic-auth/register.post")
|
|
17
|
+
});
|
|
18
|
+
addServerHandler({
|
|
19
|
+
route: "/api/basic-auth/sign-out",
|
|
20
|
+
method: "post",
|
|
21
|
+
handler: resolve("runtime/server/api/basic-auth/sign-out.post")
|
|
22
|
+
});
|
|
23
|
+
addServerHandler({
|
|
24
|
+
route: "/api/basic-auth/refresh",
|
|
25
|
+
method: "post",
|
|
26
|
+
handler: resolve("runtime/server/api/basic-auth/refresh.post")
|
|
27
|
+
});
|
|
28
|
+
addServerHandler({
|
|
29
|
+
route: "/api/basic-auth/change-password",
|
|
30
|
+
method: "post",
|
|
31
|
+
handler: resolve("runtime/server/api/basic-auth/change-password.post")
|
|
32
|
+
});
|
|
33
|
+
addPlugin(resolve("runtime/plugins/basic-auth-ui.client"), {
|
|
34
|
+
append: true
|
|
35
|
+
});
|
|
36
|
+
addPlugin(resolve("runtime/plugins/auth-status.client"), {
|
|
37
|
+
append: true
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export { module$1 as default };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
modelValue: boolean;
|
|
3
|
+
username?: string;
|
|
4
|
+
};
|
|
5
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
6
|
+
"update:modelValue": (value: boolean) => any;
|
|
7
|
+
updated: () => any;
|
|
8
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
9
|
+
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
|
|
10
|
+
onUpdated?: (() => any) | undefined;
|
|
11
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
12
|
+
export default _default;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UModal
|
|
3
|
+
v-model:open="isOpen"
|
|
4
|
+
title="Change Password"
|
|
5
|
+
description="Update your account password"
|
|
6
|
+
:ui="modalUi"
|
|
7
|
+
>
|
|
8
|
+
<template #body>
|
|
9
|
+
<UForm :state="state" @submit.prevent="onSubmit">
|
|
10
|
+
<div class="flex flex-col gap-4">
|
|
11
|
+
<input
|
|
12
|
+
:value="props.username ?? ''"
|
|
13
|
+
type="text"
|
|
14
|
+
autocomplete="username"
|
|
15
|
+
tabindex="-1"
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
class="sr-only pointer-events-none h-0 w-0 opacity-0"
|
|
18
|
+
readonly
|
|
19
|
+
/>
|
|
20
|
+
|
|
21
|
+
<UFormField label="Current Password" name="currentPassword">
|
|
22
|
+
<UInput
|
|
23
|
+
v-model="state.currentPassword"
|
|
24
|
+
type="password"
|
|
25
|
+
placeholder="••••••••"
|
|
26
|
+
autocomplete="current-password"
|
|
27
|
+
required
|
|
28
|
+
class="w-full"
|
|
29
|
+
/>
|
|
30
|
+
</UFormField>
|
|
31
|
+
|
|
32
|
+
<USeparator />
|
|
33
|
+
|
|
34
|
+
<UFormField label="New Password" name="newPassword">
|
|
35
|
+
<UInput
|
|
36
|
+
v-model="state.newPassword"
|
|
37
|
+
type="password"
|
|
38
|
+
placeholder="••••••••"
|
|
39
|
+
autocomplete="new-password"
|
|
40
|
+
required
|
|
41
|
+
class="w-full"
|
|
42
|
+
/>
|
|
43
|
+
</UFormField>
|
|
44
|
+
|
|
45
|
+
<UFormField label="Confirm New Password" name="confirmNewPassword">
|
|
46
|
+
<UInput
|
|
47
|
+
v-model="state.confirmNewPassword"
|
|
48
|
+
type="password"
|
|
49
|
+
placeholder="••••••••"
|
|
50
|
+
autocomplete="new-password"
|
|
51
|
+
required
|
|
52
|
+
class="w-full"
|
|
53
|
+
/>
|
|
54
|
+
</UFormField>
|
|
55
|
+
|
|
56
|
+
<div
|
|
57
|
+
v-if="errorMessage"
|
|
58
|
+
class="flex items-start gap-2 rounded-[var(--md-border-radius)] border border-[var(--md-error)]/30 bg-[var(--md-error)]/8 px-3 py-2.5 text-sm text-[var(--md-error)]"
|
|
59
|
+
>
|
|
60
|
+
<UIcon name="i-lucide-alert-circle" class="w-4 h-4 mt-0.5 shrink-0" />
|
|
61
|
+
{{ errorMessage }}
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div class="flex justify-end gap-2 pt-1">
|
|
65
|
+
<UButton
|
|
66
|
+
color="neutral"
|
|
67
|
+
variant="outline"
|
|
68
|
+
type="button"
|
|
69
|
+
:disabled="pending"
|
|
70
|
+
@click="close"
|
|
71
|
+
>
|
|
72
|
+
Cancel
|
|
73
|
+
</UButton>
|
|
74
|
+
<UButton type="submit" :loading="pending">
|
|
75
|
+
Update Password
|
|
76
|
+
</UButton>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</UForm>
|
|
80
|
+
</template>
|
|
81
|
+
</UModal>
|
|
82
|
+
</template>
|
|
83
|
+
|
|
84
|
+
<script setup>
|
|
85
|
+
import { computed, reactive, ref } from "vue";
|
|
86
|
+
const modalUi = {
|
|
87
|
+
content: "sm:max-w-[400px]"
|
|
88
|
+
};
|
|
89
|
+
const props = defineProps({
|
|
90
|
+
modelValue: { type: Boolean, required: true },
|
|
91
|
+
username: { type: String, required: false }
|
|
92
|
+
});
|
|
93
|
+
const emit = defineEmits(["update:modelValue", "updated"]);
|
|
94
|
+
const pending = ref(false);
|
|
95
|
+
const errorMessage = ref("");
|
|
96
|
+
const state = reactive({
|
|
97
|
+
currentPassword: "",
|
|
98
|
+
newPassword: "",
|
|
99
|
+
confirmNewPassword: ""
|
|
100
|
+
});
|
|
101
|
+
const isOpen = computed({
|
|
102
|
+
get: () => props.modelValue,
|
|
103
|
+
set: (value) => emit("update:modelValue", value)
|
|
104
|
+
});
|
|
105
|
+
function close() {
|
|
106
|
+
errorMessage.value = "";
|
|
107
|
+
emit("update:modelValue", false);
|
|
108
|
+
}
|
|
109
|
+
async function onSubmit() {
|
|
110
|
+
if (state.newPassword !== state.confirmNewPassword) {
|
|
111
|
+
errorMessage.value = "Passwords do not match";
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
pending.value = true;
|
|
115
|
+
errorMessage.value = "";
|
|
116
|
+
try {
|
|
117
|
+
await $fetch("/api/basic-auth/change-password", {
|
|
118
|
+
method: "POST",
|
|
119
|
+
body: {
|
|
120
|
+
currentPassword: state.currentPassword,
|
|
121
|
+
newPassword: state.newPassword,
|
|
122
|
+
confirmNewPassword: state.confirmNewPassword
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
emit("updated");
|
|
126
|
+
close();
|
|
127
|
+
} catch {
|
|
128
|
+
errorMessage.value = "Unable to change password";
|
|
129
|
+
} finally {
|
|
130
|
+
pending.value = false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
</script>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
modelValue: boolean;
|
|
3
|
+
username?: string;
|
|
4
|
+
};
|
|
5
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
6
|
+
"update:modelValue": (value: boolean) => any;
|
|
7
|
+
updated: () => any;
|
|
8
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
9
|
+
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
|
|
10
|
+
onUpdated?: (() => any) | undefined;
|
|
11
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
12
|
+
export default _default;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
modelValue: boolean;
|
|
3
|
+
};
|
|
4
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
5
|
+
"update:modelValue": (value: boolean) => any;
|
|
6
|
+
registered: () => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
8
|
+
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
|
|
9
|
+
onRegistered?: (() => any) | undefined;
|
|
10
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UModal
|
|
3
|
+
v-model:open="isOpen"
|
|
4
|
+
title="Create Account"
|
|
5
|
+
description="Register a new account"
|
|
6
|
+
:close="{
|
|
7
|
+
class: 'theme-btn',
|
|
8
|
+
size: 'sm'
|
|
9
|
+
}"
|
|
10
|
+
:ui="modalUi"
|
|
11
|
+
>
|
|
12
|
+
<template #body>
|
|
13
|
+
<UForm :state="state" @submit.prevent="onSubmit">
|
|
14
|
+
<div class="flex flex-col gap-4">
|
|
15
|
+
<input
|
|
16
|
+
:value="state.email"
|
|
17
|
+
type="text"
|
|
18
|
+
autocomplete="username"
|
|
19
|
+
tabindex="-1"
|
|
20
|
+
aria-hidden="true"
|
|
21
|
+
class="sr-only pointer-events-none h-0 w-0 opacity-0"
|
|
22
|
+
readonly
|
|
23
|
+
/>
|
|
24
|
+
|
|
25
|
+
<UFormField label="Email" name="email">
|
|
26
|
+
<UInput
|
|
27
|
+
v-model="state.email"
|
|
28
|
+
type="email"
|
|
29
|
+
placeholder="you@example.com"
|
|
30
|
+
autocomplete="username"
|
|
31
|
+
required
|
|
32
|
+
class="w-full"
|
|
33
|
+
/>
|
|
34
|
+
</UFormField>
|
|
35
|
+
|
|
36
|
+
<UFormField label="Display name" name="displayName">
|
|
37
|
+
<UInput
|
|
38
|
+
v-model="state.displayName"
|
|
39
|
+
type="text"
|
|
40
|
+
placeholder="Your name"
|
|
41
|
+
autocomplete="name"
|
|
42
|
+
class="w-full"
|
|
43
|
+
/>
|
|
44
|
+
</UFormField>
|
|
45
|
+
|
|
46
|
+
<UFormField label="Password" name="password">
|
|
47
|
+
<UInput
|
|
48
|
+
v-model="state.password"
|
|
49
|
+
type="password"
|
|
50
|
+
placeholder="••••••••"
|
|
51
|
+
autocomplete="new-password"
|
|
52
|
+
required
|
|
53
|
+
class="w-full"
|
|
54
|
+
/>
|
|
55
|
+
</UFormField>
|
|
56
|
+
|
|
57
|
+
<UFormField label="Confirm password" name="confirmPassword">
|
|
58
|
+
<UInput
|
|
59
|
+
v-model="state.confirmPassword"
|
|
60
|
+
type="password"
|
|
61
|
+
placeholder="••••••••"
|
|
62
|
+
autocomplete="new-password"
|
|
63
|
+
required
|
|
64
|
+
class="w-full"
|
|
65
|
+
/>
|
|
66
|
+
</UFormField>
|
|
67
|
+
|
|
68
|
+
<UFormField label="Invite token (optional)" name="inviteToken">
|
|
69
|
+
<UInput
|
|
70
|
+
v-model="state.inviteToken"
|
|
71
|
+
type="text"
|
|
72
|
+
placeholder="paste invite token"
|
|
73
|
+
class="w-full"
|
|
74
|
+
/>
|
|
75
|
+
</UFormField>
|
|
76
|
+
|
|
77
|
+
<div
|
|
78
|
+
v-if="errorMessage"
|
|
79
|
+
class="flex items-start gap-2 rounded-[var(--md-border-radius)] border border-[var(--md-error)]/30 bg-[var(--md-error)]/8 px-3 py-2.5 text-sm text-[var(--md-error)]"
|
|
80
|
+
>
|
|
81
|
+
<UIcon name="i-lucide-alert-circle" class="w-4 h-4 mt-0.5 shrink-0" />
|
|
82
|
+
{{ errorMessage }}
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="flex justify-end gap-2 pt-1">
|
|
86
|
+
<UButton
|
|
87
|
+
color="neutral"
|
|
88
|
+
variant="outline"
|
|
89
|
+
type="button"
|
|
90
|
+
:disabled="pending"
|
|
91
|
+
@click="close"
|
|
92
|
+
>
|
|
93
|
+
Cancel
|
|
94
|
+
</UButton>
|
|
95
|
+
<UButton class="theme-btn new-act-btn" type="submit" :loading="pending">
|
|
96
|
+
Create account
|
|
97
|
+
</UButton>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</UForm>
|
|
101
|
+
</template>
|
|
102
|
+
</UModal>
|
|
103
|
+
</template>
|
|
104
|
+
|
|
105
|
+
<script setup>
|
|
106
|
+
import { computed, reactive, ref } from "vue";
|
|
107
|
+
const modalUi = {
|
|
108
|
+
content: "sm:max-w-[420px]"
|
|
109
|
+
};
|
|
110
|
+
const props = defineProps({
|
|
111
|
+
modelValue: { type: Boolean, required: true }
|
|
112
|
+
});
|
|
113
|
+
const emit = defineEmits(["update:modelValue", "registered"]);
|
|
114
|
+
const state = reactive({
|
|
115
|
+
email: "",
|
|
116
|
+
displayName: "",
|
|
117
|
+
password: "",
|
|
118
|
+
confirmPassword: "",
|
|
119
|
+
inviteToken: ""
|
|
120
|
+
});
|
|
121
|
+
const pending = ref(false);
|
|
122
|
+
const errorMessage = ref("");
|
|
123
|
+
const isOpen = computed({
|
|
124
|
+
get: () => props.modelValue,
|
|
125
|
+
set: (value) => {
|
|
126
|
+
emit("update:modelValue", value);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
function close() {
|
|
130
|
+
errorMessage.value = "";
|
|
131
|
+
emit("update:modelValue", false);
|
|
132
|
+
}
|
|
133
|
+
async function onSubmit() {
|
|
134
|
+
pending.value = true;
|
|
135
|
+
errorMessage.value = "";
|
|
136
|
+
try {
|
|
137
|
+
await $fetch("/api/basic-auth/register", {
|
|
138
|
+
method: "POST",
|
|
139
|
+
body: {
|
|
140
|
+
email: state.email,
|
|
141
|
+
displayName: state.displayName || void 0,
|
|
142
|
+
password: state.password,
|
|
143
|
+
confirmPassword: state.confirmPassword,
|
|
144
|
+
inviteToken: state.inviteToken || void 0
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
emit("registered");
|
|
148
|
+
close();
|
|
149
|
+
} catch (error) {
|
|
150
|
+
const statusCode = typeof error === "object" && error !== null && "statusCode" in error ? Number(error.statusCode) : null;
|
|
151
|
+
if (statusCode === 400) {
|
|
152
|
+
errorMessage.value = "Please check your input and try again.";
|
|
153
|
+
} else if (statusCode === 403) {
|
|
154
|
+
errorMessage.value = "Registration is restricted for this instance.";
|
|
155
|
+
} else if (statusCode === 409) {
|
|
156
|
+
errorMessage.value = "Email is already in use.";
|
|
157
|
+
} else {
|
|
158
|
+
errorMessage.value = "Unable to register right now. Please try again.";
|
|
159
|
+
}
|
|
160
|
+
} finally {
|
|
161
|
+
pending.value = false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
</script>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
modelValue: boolean;
|
|
3
|
+
};
|
|
4
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
5
|
+
"update:modelValue": (value: boolean) => any;
|
|
6
|
+
registered: () => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
8
|
+
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
|
|
9
|
+
onRegistered?: (() => any) | undefined;
|
|
10
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
modelValue: boolean;
|
|
3
|
+
};
|
|
4
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
5
|
+
"update:modelValue": (value: boolean) => any;
|
|
6
|
+
"signed-in": () => any;
|
|
7
|
+
"open-register": () => any;
|
|
8
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
9
|
+
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
|
|
10
|
+
"onSigned-in"?: (() => any) | undefined;
|
|
11
|
+
"onOpen-register"?: (() => any) | undefined;
|
|
12
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
export default _default;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UModal
|
|
3
|
+
v-model:open="isOpen"
|
|
4
|
+
title="Sign In"
|
|
5
|
+
:close="{
|
|
6
|
+
class: 'theme-btn',
|
|
7
|
+
size: 'sm'
|
|
8
|
+
}"
|
|
9
|
+
description="Enter your credentials to continue"
|
|
10
|
+
:ui="modalUi"
|
|
11
|
+
>
|
|
12
|
+
<template #body>
|
|
13
|
+
<UForm :state="state" @submit.prevent="onSubmit">
|
|
14
|
+
<div class="flex flex-col gap-4">
|
|
15
|
+
<input
|
|
16
|
+
:value="state.email"
|
|
17
|
+
type="text"
|
|
18
|
+
autocomplete="username"
|
|
19
|
+
tabindex="-1"
|
|
20
|
+
aria-hidden="true"
|
|
21
|
+
class="sr-only pointer-events-none h-0 w-0 opacity-0"
|
|
22
|
+
readonly
|
|
23
|
+
/>
|
|
24
|
+
|
|
25
|
+
<UFormField label="Email" name="email">
|
|
26
|
+
<UInput
|
|
27
|
+
v-model="state.email"
|
|
28
|
+
type="email"
|
|
29
|
+
placeholder="you@example.com"
|
|
30
|
+
autocomplete="username"
|
|
31
|
+
required
|
|
32
|
+
class="w-full"
|
|
33
|
+
/>
|
|
34
|
+
</UFormField>
|
|
35
|
+
|
|
36
|
+
<UFormField label="Password" name="password">
|
|
37
|
+
<UInput
|
|
38
|
+
v-model="state.password"
|
|
39
|
+
type="password"
|
|
40
|
+
placeholder="••••••••"
|
|
41
|
+
autocomplete="current-password"
|
|
42
|
+
required
|
|
43
|
+
class="w-full"
|
|
44
|
+
/>
|
|
45
|
+
</UFormField>
|
|
46
|
+
|
|
47
|
+
<div
|
|
48
|
+
v-if="errorMessage"
|
|
49
|
+
class="flex items-start gap-2 rounded-[var(--md-border-radius)] border border-[var(--md-error)]/30 bg-[var(--md-error)]/8 px-3 py-2.5 text-sm text-[var(--md-error)]"
|
|
50
|
+
>
|
|
51
|
+
<UIcon name="i-lucide-alert-circle" class="w-4 h-4 mt-0.5 shrink-0" />
|
|
52
|
+
{{ errorMessage }}
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="flex justify-end gap-2 pt-1">
|
|
56
|
+
<UButton
|
|
57
|
+
class="theme-btn create-act-si-btn"
|
|
58
|
+
color="neutral"
|
|
59
|
+
variant="ghost"
|
|
60
|
+
type="button"
|
|
61
|
+
:disabled="pending"
|
|
62
|
+
@click="emit('open-register')"
|
|
63
|
+
>
|
|
64
|
+
Create account
|
|
65
|
+
</UButton>
|
|
66
|
+
<UButton
|
|
67
|
+
color="neutral"
|
|
68
|
+
variant="outline"
|
|
69
|
+
type="button"
|
|
70
|
+
:disabled="pending"
|
|
71
|
+
@click="close"
|
|
72
|
+
>
|
|
73
|
+
Cancel
|
|
74
|
+
</UButton>
|
|
75
|
+
<UButton type="submit" :loading="pending">
|
|
76
|
+
Sign In
|
|
77
|
+
</UButton>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</UForm>
|
|
81
|
+
</template>
|
|
82
|
+
</UModal>
|
|
83
|
+
</template>
|
|
84
|
+
|
|
85
|
+
<script setup>
|
|
86
|
+
import { computed, reactive, ref } from "vue";
|
|
87
|
+
const modalUi = {
|
|
88
|
+
content: "sm:max-w-[380px]"
|
|
89
|
+
};
|
|
90
|
+
const props = defineProps({
|
|
91
|
+
modelValue: { type: Boolean, required: true }
|
|
92
|
+
});
|
|
93
|
+
const emit = defineEmits(["update:modelValue", "signed-in", "open-register"]);
|
|
94
|
+
const state = reactive({
|
|
95
|
+
email: "",
|
|
96
|
+
password: ""
|
|
97
|
+
});
|
|
98
|
+
const pending = ref(false);
|
|
99
|
+
const errorMessage = ref("");
|
|
100
|
+
const isOpen = computed({
|
|
101
|
+
get: () => props.modelValue,
|
|
102
|
+
set: (value) => {
|
|
103
|
+
emit("update:modelValue", value);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
function close() {
|
|
107
|
+
errorMessage.value = "";
|
|
108
|
+
emit("update:modelValue", false);
|
|
109
|
+
}
|
|
110
|
+
async function onSubmit() {
|
|
111
|
+
pending.value = true;
|
|
112
|
+
errorMessage.value = "";
|
|
113
|
+
try {
|
|
114
|
+
await $fetch("/api/basic-auth/sign-in", {
|
|
115
|
+
method: "POST",
|
|
116
|
+
body: {
|
|
117
|
+
email: state.email,
|
|
118
|
+
password: state.password
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
emit("signed-in");
|
|
122
|
+
close();
|
|
123
|
+
} catch (error) {
|
|
124
|
+
const statusCode = typeof error === "object" && error !== null && "statusCode" in error ? Number(error.statusCode) : null;
|
|
125
|
+
if (statusCode === 400) {
|
|
126
|
+
errorMessage.value = "Please enter a valid email and password.";
|
|
127
|
+
} else if (statusCode === 401) {
|
|
128
|
+
errorMessage.value = "Invalid credentials";
|
|
129
|
+
} else {
|
|
130
|
+
errorMessage.value = "Unable to sign in right now. Please try again.";
|
|
131
|
+
}
|
|
132
|
+
} finally {
|
|
133
|
+
pending.value = false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
</script>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
modelValue: boolean;
|
|
3
|
+
};
|
|
4
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
5
|
+
"update:modelValue": (value: boolean) => any;
|
|
6
|
+
"signed-in": () => any;
|
|
7
|
+
"open-register": () => any;
|
|
8
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
9
|
+
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
|
|
10
|
+
"onSigned-in"?: (() => any) | undefined;
|
|
11
|
+
"onOpen-register"?: (() => any) | undefined;
|
|
12
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
export default _default;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
email?: string;
|
|
3
|
+
displayName?: string;
|
|
4
|
+
};
|
|
5
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
6
|
+
"signed-out": () => any;
|
|
7
|
+
"change-password": () => any;
|
|
8
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
9
|
+
"onSigned-out"?: (() => any) | undefined;
|
|
10
|
+
"onChange-password"?: (() => any) | undefined;
|
|
11
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
12
|
+
export default _default;
|