@mframework/layer-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.
@@ -0,0 +1,14 @@
1
+ <template>
2
+ <v-btn variant="flat" @click="signOut">Logout</v-btn>
3
+ </template>
4
+
5
+ <script setup>
6
+ const { logout } = useAuth()
7
+ const router = useRouter()
8
+
9
+ const signOut = async () => {
10
+ await logout()
11
+ await router.push('/login')
12
+ }
13
+ </script>
14
+
@@ -0,0 +1,159 @@
1
+ import type {
2
+ Subscription
3
+ } from '@better-auth/stripe'
4
+ import type {
5
+ CustomerState
6
+ } from '@polar-sh/sdk/models/components/customerstate.js'
7
+ import type {
8
+ BetterAuthClientOptions,
9
+ InferSessionFromClient,
10
+ User
11
+ } from 'better-auth/client'
12
+ import {
13
+ createAuthClient
14
+ } from 'better-auth/client'
15
+ import {
16
+ getAuthPlugins
17
+ } from '../utils/plugins'
18
+
19
+ export type UseAuthOptions = {
20
+ /** An existing auth client. If provided, it's used as-is. */
21
+ client ? : any
22
+ /** Base URL for the auth client if creating one. */
23
+ baseURL ? : string
24
+ /** Optional headers to send with requests. */
25
+ headers ? : Record < string,
26
+ string >
27
+ /** Payment provider: 'stripe' or 'polar'. If omitted, 'stripe' is assumed. */
28
+ payment ? : 'stripe' | 'polar'
29
+ /** Optional app-level reload/redirect function (framework-specific). */
30
+ reload ? : (opts: {
31
+ path ? : string
32
+ }) => Promise < void >
33
+ }
34
+
35
+ export function useAuth(options: UseAuthOptions = {}) {
36
+ const {
37
+ client: providedClient,
38
+ baseURL,
39
+ headers,
40
+ payment = 'stripe',
41
+ reload
42
+ } = options
43
+
44
+ const client = providedClient || createAuthClient({
45
+ baseURL: baseURL || undefined,
46
+ fetchOptions: {
47
+ headers
48
+ },
49
+ socialProviders: {
50
+ github: {
51
+ clientId: process.env.GITHUB_CLIENT_ID!,
52
+ clientSecret: process.env.GITHUB_CLIENT_SECRET!
53
+ },
54
+ microsoft: {
55
+ clientId: process.env.MICROSOFT_CLIENT_ID!,
56
+ clientSecret: process.env.MICROSOFT_CLIENT_SECRET!
57
+ },
58
+ twitter: {
59
+ clientId: process.env.TWITTER_CLIENT_ID!,
60
+ clientSecret: process.env.TWITTER_CLIENT_SECRET!
61
+ }
62
+ },
63
+ plugins: getAuthPlugins({
64
+ subscription: true
65
+ })
66
+ })
67
+
68
+ let session: InferSessionFromClient < BetterAuthClientOptions > | null = null
69
+ let user: User | null = null
70
+ let subscriptions: Subscription[] = []
71
+ let polarState: CustomerState | null = null
72
+ let sessionFetching = false
73
+
74
+ const fetchSession = async () => {
75
+ if (sessionFetching) return
76
+ sessionFetching = true
77
+ const {
78
+ data
79
+ } = await client.getSession()
80
+ session = data?.session || null
81
+
82
+ const userDefaults = {
83
+ image: null,
84
+ role: null,
85
+ banReason: null,
86
+ banned: null,
87
+ banExpires: null,
88
+ stripeCustomerId: null
89
+ }
90
+ user = data?.user ? Object.assign({}, userDefaults, data.user) : null
91
+ subscriptions = []
92
+ if (user) {
93
+ if (payment == 'stripe') {
94
+ const {
95
+ data: subscriptionData
96
+ } = await client.subscription.list()
97
+ subscriptions = subscriptionData || []
98
+ } else if (payment == 'polar') {
99
+ const {
100
+ data: customerState
101
+ } = await client.customer.state()
102
+ polarState = customerState
103
+ }
104
+ }
105
+ sessionFetching = false
106
+ return data
107
+ }
108
+
109
+ if (client?.$store?.listen) {
110
+ client.$store.listen('$sessionSignal', async (signal: any) => {
111
+ if (!signal) return
112
+ await fetchSession()
113
+ })
114
+ }
115
+
116
+ return {
117
+ session,
118
+ user,
119
+ subscription: client.subscription,
120
+ subscriptions,
121
+ loggedIn: () => !!session,
122
+ activeStripeSubscription: () => {
123
+ return subscriptions.find((sub: {
124
+ status: string
125
+ }) => sub.status === 'active' || sub.status === 'trialing')
126
+ },
127
+ activePolarSubscriptions: () => {
128
+ return polarState?.activeSubscriptions
129
+ },
130
+ signIn: client.signIn,
131
+ signUp: client.signUp,
132
+ forgetPassword: client.forgetPassword,
133
+ resetPassword: client.resetPassword,
134
+ sendVerificationEmail: client.sendVerificationEmail,
135
+ errorCodes: client.$ERROR_CODES,
136
+ async signOut({
137
+ redirectTo
138
+ }: {
139
+ redirectTo ? : string
140
+ } = {}) {
141
+ await client.signOut({
142
+ fetchOptions: {
143
+ onSuccess: async () => {
144
+ session = null
145
+ user = null
146
+ if (redirectTo && typeof reload === 'function') {
147
+ await reload({
148
+ path: redirectTo.toString()
149
+ })
150
+ }
151
+ }
152
+ }
153
+ })
154
+ },
155
+ fetchSession,
156
+ payment,
157
+ client
158
+ }
159
+ }
@@ -0,0 +1,65 @@
1
+ import { ref } from 'vue';
2
+
3
+ // `base-app` generated Prisma types may not exist in this workspace during isolated
4
+ // builds. Use a local minimal `User` alias to keep the layer buildable.
5
+ type User = any
6
+
7
+ const isEditOpen = ref(false);
8
+ const isCreateOpen = ref(false);
9
+ const isDeleteOpen = ref(false);
10
+ const isLoading = ref(false);
11
+ const userToEdit = ref<User | null>(null);
12
+
13
+ export const useUser = () => {
14
+
15
+ const closeOtherModals = () => {
16
+ userToEdit.value = null;
17
+ isEditOpen.value = false;
18
+ isDeleteOpen.value = false;
19
+ isCreateOpen.value = false;
20
+ };
21
+
22
+
23
+ const openEditUser = (user: User) => {
24
+ closeOtherModals();
25
+ userToEdit.value = user;
26
+ isEditOpen.value = true;
27
+ };
28
+
29
+ const closeEditUser = () => {
30
+ isEditOpen.value = false;
31
+ userToEdit.value = null;
32
+ };
33
+
34
+ const openDeleteUser = () => {
35
+ closeOtherModals();
36
+ isDeleteOpen.value = true;
37
+ };
38
+
39
+ const closeDeleteUser = () => {
40
+ isDeleteOpen.value = false;
41
+ };
42
+
43
+ const openAddUser = () => {
44
+ closeOtherModals();
45
+ isCreateOpen.value = true;
46
+ };
47
+ const closeCreateUser = () => {
48
+ closeOtherModals();
49
+ isCreateOpen.value = false;
50
+ };
51
+
52
+ return {
53
+ isEditOpen,
54
+ isDeleteOpen,
55
+ isLoading,
56
+ userToEdit,
57
+ openEditUser,
58
+ closeEditUser,
59
+ openDeleteUser,
60
+ closeDeleteUser,
61
+ openAddUser,
62
+ isCreateOpen,
63
+ closeCreateUser
64
+ };
65
+ };
@@ -0,0 +1,8 @@
1
+ <template>
2
+ <div>
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup>
8
+ </script>
@@ -0,0 +1,7 @@
1
+ import { defineNuxtRouteMiddleware, navigateTo } from "nuxt/app"
2
+
3
+ export default defineNuxtRouteMiddleware((to) => {
4
+ if (to.path !== '/' && to.path.endsWith('/')) {
5
+ return navigateTo(to.path.replace(/\/+$/, '') || '/')
6
+ }
7
+ })
@@ -0,0 +1,7 @@
1
+ import { defineNuxtRouteMiddleware } from "nuxt/app";
2
+
3
+ export default defineNuxtRouteMiddleware((to) => {
4
+ if (to.path.startsWith('/dashboard/')) {
5
+ // seller check here
6
+ }
7
+ })
@@ -0,0 +1,12 @@
1
+ import { defineNuxtRouteMiddleware, useCookie } from "nuxt/app"
2
+
3
+ export default defineNuxtRouteMiddleware(() => {
4
+ const session = useCookie('session')
5
+
6
+ if (!session.value) {
7
+ session.value = {
8
+ id: crypto.randomUUID(),
9
+ date_created: new Date().toISOString()
10
+ } as any
11
+ }
12
+ })
package/app/module.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { defineNuxtModule, addPlugin, createResolver } from '@nuxt/kit'
2
+
3
+ // Module options TypeScript interface definition
4
+ export interface ModuleOptions {}
5
+
6
+ export default defineNuxtModule<ModuleOptions>({
7
+ meta: {
8
+ name: 'meeovi-auth',
9
+ configKey: 'meeoviAuth',
10
+ },
11
+ // Default configuration options of the Nuxt module
12
+ defaults: {},
13
+ setup(_options, _nuxt) {
14
+ const resolver = createResolver(import.meta.url)
15
+
16
+ // Do not add the extension since the `.ts` will be transpiled to `.mjs` after `npm run prepack`
17
+ addPlugin(resolver.resolve('./runtime/plugin'))
18
+ },
19
+ })
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <div>Processing login...</div>
3
+ </template>
4
+
5
+ <script setup>
6
+ const router = useRouter()
7
+ const store = useUserStore()
8
+
9
+ onMounted(async () => {
10
+ try {
11
+ // No server session to refresh; rely on local store state
12
+ if (store.user) {
13
+ await router.push('/')
14
+ } else {
15
+ await router.push('/login')
16
+ }
17
+ } catch (error) {
18
+ console.error('Callback navigation failed:', error)
19
+ await router.push('/login')
20
+ }
21
+ })
22
+ </script>
@@ -0,0 +1,19 @@
1
+ <template>
2
+ <div>Waiting for login...</div>
3
+ </template>
4
+
5
+ <script setup>
6
+ import { navigateTo } from '#app'
7
+ import { watch } from 'vue'
8
+ import { useUserStore } from '../stores/user'
9
+
10
+ const store = useUserStore()
11
+
12
+ watch(() => store.user, (u) => {
13
+ if (u) return navigateTo('/')
14
+ }, { immediate: true })
15
+
16
+ setTimeout(() => {
17
+ if (!store.user) navigateTo('/login')
18
+ }, 3000)
19
+ </script>
@@ -0,0 +1,156 @@
1
+ <template>
2
+ <div class="authPage">
3
+ <section data-bs-version="5.1" class="authForm">
4
+ <NuxtImg loading="lazy" src="~/assets/images/logo512alpha-128x128.png" alt="Meeovi Logo" class="authLogo" />
5
+ <h1 class="mbr-section-title mbr-fonts-style display-1">Forgot Password</h1>
6
+
7
+ <div class="mbr-section-btn">
8
+ <div class="request-reset-form">
9
+ <p>Enter your email address to receive a password reset link.</p>
10
+ <form class="mbr-section-btn" :schema="schema" :state="state" @submit="onSubmit">
11
+ <v-text-field v-model="state.email" type="email" label="Email" required></v-text-field>
12
+ <div class="mb-3">
13
+ <div ref="turnstileRef"></div>
14
+ </div>
15
+ <v-btn class="mt-2 btn btn-primary display-4" type="submit" block :loading="loading" :disabled="loading || !turnstileToken">
16
+ Send Reset Link
17
+ </v-btn>
18
+ </form>
19
+ </div>
20
+
21
+ <v-alert v-if="message" :type="messageType" class="mt-4" variant="tonal" closable>
22
+ {{ message }}
23
+ </v-alert>
24
+
25
+ <div class="mt-4 text-center">
26
+ <p>Remember your password?
27
+ <NuxtLink to="/login">Sign In</NuxtLink>
28
+ </p>
29
+ </div>
30
+ </div>
31
+ </section>
32
+ </div>
33
+ </template>
34
+
35
+ <script setup>
36
+ import { useHead } from 'nuxt/app'
37
+ import { definePageMeta, useLocalePath, useAuth } from '#imports'
38
+ import { z } from 'zod'
39
+ import { reactive, ref } from 'vue'
40
+
41
+ definePageMeta({
42
+ auth: {
43
+ only: 'guest'
44
+ }
45
+ })
46
+
47
+ useHead({
48
+ title: 'Forgot Password'
49
+ })
50
+
51
+ const auth = useAuth()
52
+ const toast = useToast()
53
+ const localePath = useLocalePath()
54
+
55
+ const schema = z.object({
56
+ email: z.email(('forgotPassword.errors.invalidEmail'))
57
+ })
58
+
59
+ const state = reactive<Partial<Schema>>({
60
+ email: undefined
61
+ })
62
+
63
+ const loading = ref(false)
64
+
65
+ // expose message and messageType used by the template v-alert
66
+ const message = ref<string | null>(null)
67
+ const messageType = ref<'info' | 'success' | 'error' | 'warning' | undefined>(undefined)
68
+
69
+ // Turnstile placeholders referenced in the template
70
+ const turnstileRef = ref<HTMLElement | null>(null)
71
+ const turnstileToken = ref<string | null>(null)
72
+
73
+ async function onSubmit(event) {
74
+ event.preventDefault()
75
+ if (loading.value)
76
+ return
77
+
78
+ loading.value = true
79
+ // clear previous messages
80
+ message.value = null
81
+ messageType.value = undefined
82
+
83
+ const { error } = await auth.forgetPassword({
84
+ email: state.email,
85
+ redirectTo: localePath('/reset-password')
86
+ })
87
+
88
+ if (error) {
89
+ const text = error.message || error.statusText
90
+ // update v-alert bindings
91
+ message.value = text
92
+ messageType.value = 'error'
93
+ toast.show({
94
+ title: text,
95
+ color: 'error'
96
+ })
97
+ }
98
+ else {
99
+ const successText = ('forgotPassword.success')
100
+ // update v-alert bindings
101
+ message.value = successText
102
+ messageType.value = 'success'
103
+ toast.show({
104
+ title: successText,
105
+ color: 'success'
106
+ })
107
+ }
108
+ loading.value = false
109
+ }
110
+ </script>
111
+
112
+ <style scoped>
113
+ .authPage {
114
+ min-height: 100vh;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ background-color: var(--v-background);
119
+ }
120
+
121
+ .authForm {
122
+ width: 100%;
123
+ max-width: 400px;
124
+ margin: 2rem auto;
125
+ padding: 2rem;
126
+ background: var(--v-surface-variant);
127
+ border-radius: 8px;
128
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
129
+ }
130
+
131
+ .authLogo {
132
+ display: block;
133
+ margin: 0 auto 2rem;
134
+ max-width: 128px;
135
+ height: auto;
136
+ }
137
+
138
+ .mbr-section-title {
139
+ text-align: center;
140
+ margin-bottom: 2rem;
141
+ font-size: 2rem;
142
+ font-weight: 600;
143
+ }
144
+
145
+ .v-alert {
146
+ margin-top: 1rem;
147
+ }
148
+
149
+ .text-center {
150
+ text-align: center;
151
+ }
152
+
153
+ .mt-4 {
154
+ margin-top: 1rem;
155
+ }
156
+ </style>
@@ -0,0 +1,111 @@
1
+ {
2
+ "en": {
3
+ "signIn": {
4
+ "welcome": "Welcome to {name}",
5
+ "email": "Email",
6
+ "password": "Password",
7
+ "emailPlaceholder": "Email Address",
8
+ "passwordPlaceholder": "Password",
9
+ "rememberMe": "Remember me",
10
+ "forgotPassword": "Forgot your password?",
11
+ "signIn": "Sign In",
12
+ "noAccount": "Don't have an account?",
13
+ "createAccount": "Create today!",
14
+ "or": "Or",
15
+ "signInSuccess": "Sign in Success",
16
+ "errors": {
17
+ "invalidEmail": "Invalid email address",
18
+ "passwordLength": "Password must be at least {min} characters"
19
+ },
20
+ "emailNotVerified": "Email Not Verified",
21
+ "emailNotVerifiedDesc": "Please verify your email address to continue.",
22
+ "sendEmail": "Send Verification Email",
23
+ "sendEmailSuccess": "Verification email has been sent",
24
+ "sendEmailError": "Failed to send verification email"
25
+ }
26
+ },
27
+ "zh-CN": {
28
+ "signIn": {
29
+ "welcome": "欢迎使用 {name}",
30
+ "email": "邮箱",
31
+ "password": "密码",
32
+ "emailPlaceholder": "邮箱地址",
33
+ "passwordPlaceholder": "密码",
34
+ "rememberMe": "记住我",
35
+ "forgotPassword": "忘记密码?",
36
+ "signIn": "登录",
37
+ "noAccount": "还没有账号?",
38
+ "createAccount": "立即注册!",
39
+ "or": "或者",
40
+ "signInSuccess": "登录成功",
41
+ "errors": {
42
+ "invalidEmail": "无效的邮箱地址",
43
+ "passwordLength": "密码长度至少为{min}个字符"
44
+ },
45
+ "emailNotVerified": "邮箱未验证",
46
+ "emailNotVerifiedDesc": "请验证您的邮箱地址以继续。",
47
+ "resendEmail": "重新发送验证邮件",
48
+ "resendEmailSuccess": "验证邮件已发送",
49
+ "resendEmailError": "发送验证邮件失败",
50
+ "sendEmail": "发送验证邮件",
51
+ "sendEmailSuccess": "验证邮件已发送",
52
+ "sendEmailError": "发送验证邮件失败"
53
+ }
54
+ },
55
+ "ja": {
56
+ "signIn": {
57
+ "welcome": "{name}へようこそ",
58
+ "email": "メールアドレス",
59
+ "password": "パスワード",
60
+ "emailPlaceholder": "メールアドレス",
61
+ "passwordPlaceholder": "パスワード",
62
+ "rememberMe": "ログイン状態を保持",
63
+ "forgotPassword": "パスワードをお忘れですか?",
64
+ "signIn": "ログイン",
65
+ "noAccount": "アカウントをお持ちでない方",
66
+ "createAccount": "新規登録!",
67
+ "or": "または",
68
+ "signInSuccess": "ログインに成功しました",
69
+ "errors": {
70
+ "invalidEmail": "無効なメールアドレス",
71
+ "passwordLength": "パスワードは{min}文字以上である必要があります"
72
+ },
73
+ "emailNotVerified": "メールアドレス未確認",
74
+ "emailNotVerifiedDesc": "続行するにはメールアドレスを確認してください。",
75
+ "resendEmail": "確認メールを再送信",
76
+ "resendEmailSuccess": "確認メールを送信しました",
77
+ "resendEmailError": "確認メールの送信に失敗しました",
78
+ "sendEmail": "認証メールを送信",
79
+ "sendEmailSuccess": "認証メールを送信しました",
80
+ "sendEmailError": "認証メールの送信に失敗しました"
81
+ }
82
+ },
83
+ "fr": {
84
+ "signIn": {
85
+ "welcome": "Bienvenue sur {name}",
86
+ "email": "E-mail",
87
+ "password": "Mot de passe",
88
+ "emailPlaceholder": "Adresse e-mail",
89
+ "passwordPlaceholder": "Mot de passe",
90
+ "rememberMe": "Se souvenir de moi",
91
+ "forgotPassword": "Mot de passe oublié ?",
92
+ "signIn": "Se connecter",
93
+ "noAccount": "Vous n'avez pas de compte ?",
94
+ "createAccount": "Créez-en un !",
95
+ "or": "Ou",
96
+ "signInSuccess": "Connexion réussie",
97
+ "errors": {
98
+ "invalidEmail": "Adresse e-mail invalide",
99
+ "passwordLength": "Le mot de passe doit contenir au moins {min} caractères"
100
+ },
101
+ "emailNotVerified": "Email non vérifié",
102
+ "emailNotVerifiedDesc": "Veuillez vérifier votre adresse e-mail pour continuer.",
103
+ "resendEmail": "Renvoyer l'e-mail de vérification",
104
+ "resendEmailSuccess": "L'e-mail de vérification a été envoyé",
105
+ "resendEmailError": "Échec de l'envoi de l'e-mail de vérification",
106
+ "sendEmail": "Envoyer l'e-mail de vérification",
107
+ "sendEmailSuccess": "L'e-mail de vérification a été envoyé",
108
+ "sendEmailError": "Échec de l'envoi de l'e-mail de vérification"
109
+ }
110
+ }
111
+ }