@meeovi/auth 1.0.0 → 1.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 +56 -0
- package/package.json +5 -2
- package/src/config.ts +3 -1
- package/src/framework.ts +24 -0
- package/src/plugins.ts +25 -0
- package/src/providers/better-auth.ts +107 -0
- package/src/registry.ts +22 -0
- package/src/types.ts +33 -0
- package/src/useAuth.ts +104 -10
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
|
|
2
|
+
---
|
|
3
|
+
|
|
4
|
+
# 📦 `@meeovi/auth` — README.md
|
|
5
|
+
|
|
6
|
+
```md
|
|
7
|
+
# @meeovi/auth
|
|
8
|
+
|
|
9
|
+
A unified authentication abstraction for Meeovi.
|
|
10
|
+
Supports Better Auth, Auth.js, Lucia, Ory, and custom auth backends.
|
|
11
|
+
|
|
12
|
+
## ✨ Features
|
|
13
|
+
|
|
14
|
+
- Unified `useAuth()` composable
|
|
15
|
+
- Pluggable auth providers
|
|
16
|
+
- Runtime configuration
|
|
17
|
+
- Session helpers
|
|
18
|
+
|
|
19
|
+
## 📦 Installation
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install @meeovi/auth
|
|
23
|
+
|
|
24
|
+
⚙️ Configuration
|
|
25
|
+
|
|
26
|
+
import { setAuthConfig } from '@meeovi/auth'
|
|
27
|
+
|
|
28
|
+
setAuthConfig({
|
|
29
|
+
authProvider: 'better-auth',
|
|
30
|
+
authUrl: '/api/auth'
|
|
31
|
+
})
|
|
32
|
+
🧩 Usage
|
|
33
|
+
|
|
34
|
+
import { useAuth } from '@meeovi/auth'
|
|
35
|
+
|
|
36
|
+
const { login, logout, session } = useAuth()
|
|
37
|
+
|
|
38
|
+
await login({ email, password })
|
|
39
|
+
🔌 Providers
|
|
40
|
+
|
|
41
|
+
export interface AuthProvider {
|
|
42
|
+
login(credentials: any): Promise<any>
|
|
43
|
+
logout(): Promise<void>
|
|
44
|
+
session(): Promise<any>
|
|
45
|
+
}
|
|
46
|
+
Register:
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
registerAuthProvider('better-auth', { login, logout, session })
|
|
50
|
+
🧱 Folder Structure
|
|
51
|
+
Code
|
|
52
|
+
src/
|
|
53
|
+
providers/
|
|
54
|
+
config.
|
|
55
|
+
registry.
|
|
56
|
+
useAuth.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meeovi/auth",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Authentication for M Framework.",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "",
|
|
@@ -15,10 +15,13 @@
|
|
|
15
15
|
"@better-auth/passkey": "^1.4.10",
|
|
16
16
|
"@better-auth/scim": "^1.4.15",
|
|
17
17
|
"@better-auth/sso": "^1.4.12",
|
|
18
|
+
"@better-auth/stripe": "^1.4.15",
|
|
18
19
|
"@polar-sh/better-auth": "^1.6.3",
|
|
19
20
|
"better-auth": "^1.4.12",
|
|
20
21
|
"mjml": "^4.18.0",
|
|
21
22
|
"uncrypto": "^0.1.3",
|
|
22
|
-
"uuid": "^13.0.0"
|
|
23
|
+
"uuid": "^13.0.0",
|
|
24
|
+
"vue": "^3.5.27",
|
|
25
|
+
"vue-router": "^4.6.4"
|
|
23
26
|
}
|
|
24
27
|
}
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
export interface AuthConfig {
|
|
3
|
+
baseUrl: any
|
|
3
4
|
authProvider: string
|
|
4
5
|
authUrl?: string
|
|
5
6
|
sessionCookieName?: string
|
|
@@ -8,7 +9,8 @@ export interface AuthConfig {
|
|
|
8
9
|
let config: AuthConfig = {
|
|
9
10
|
authProvider: 'better-auth',
|
|
10
11
|
authUrl: '',
|
|
11
|
-
sessionCookieName: '
|
|
12
|
+
sessionCookieName: 'session',
|
|
13
|
+
baseUrl: undefined
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export function setAuthConfig(newConfig: Partial<AuthConfig>) {
|
package/src/framework.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
export interface FrameworkContext {
|
|
3
|
+
getRequestURL?(): string
|
|
4
|
+
getRequestHeaders?(): Record<string, string>
|
|
5
|
+
getConfig?(): any
|
|
6
|
+
reloadApp?(): void
|
|
7
|
+
state?<T>(key: string, init: () => T): { value: T }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let ctx: FrameworkContext = {}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Frameworks (Nuxt, React, etc.) call this once during initialization.
|
|
14
|
+
*/
|
|
15
|
+
export function setFrameworkContext(newCtx: FrameworkContext) {
|
|
16
|
+
ctx = { ...ctx, ...newCtx }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Auth providers use this to access framework-specific helpers.
|
|
21
|
+
*/
|
|
22
|
+
export function getFrameworkContext(): FrameworkContext {
|
|
23
|
+
return ctx
|
|
24
|
+
}
|
package/src/plugins.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { stripeClient } from '@better-auth/stripe/client'
|
|
2
|
+
import { polarClient } from '@polar-sh/better-auth'
|
|
3
|
+
import { adminClient, inferAdditionalFields } from 'better-auth/client/plugins'
|
|
4
|
+
|
|
5
|
+
export type AuthPluginOptions = {
|
|
6
|
+
/** whether to enable subscription support for stripe plugin */
|
|
7
|
+
subscription?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getAuthPlugins(opts: AuthPluginOptions = {}) {
|
|
11
|
+
const { subscription = true } = opts
|
|
12
|
+
|
|
13
|
+
return [
|
|
14
|
+
inferAdditionalFields({
|
|
15
|
+
user: {
|
|
16
|
+
polarCustomerId: {
|
|
17
|
+
type: 'string'
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}),
|
|
21
|
+
adminClient(),
|
|
22
|
+
polarClient(),
|
|
23
|
+
stripeClient({ subscription })
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as BetterAuth from 'better-auth'
|
|
2
|
+
import { registerAuthProvider } from '../registry'
|
|
3
|
+
import type { AuthProvider } from '../types'
|
|
4
|
+
import { getAuthConfig } from '../config'
|
|
5
|
+
import { getFrameworkContext } from '../framework'
|
|
6
|
+
|
|
7
|
+
// Create a single shared Better Auth client instance
|
|
8
|
+
let client: any = null
|
|
9
|
+
|
|
10
|
+
function resolveClientFactory() {
|
|
11
|
+
return (BetterAuth as any).Client ?? (BetterAuth as any).default ?? BetterAuth
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getClient() {
|
|
15
|
+
if (client) return client
|
|
16
|
+
|
|
17
|
+
const config = getAuthConfig()
|
|
18
|
+
const ctx = getFrameworkContext()
|
|
19
|
+
|
|
20
|
+
const Client = resolveClientFactory()
|
|
21
|
+
client = Client({
|
|
22
|
+
baseURL: config.baseUrl,
|
|
23
|
+
fetch: async (url: string | Request | URL, options: RequestInit = {}) => {
|
|
24
|
+
// Framework‑agnostic request wrapper
|
|
25
|
+
const baseHeaders = ctx.getRequestHeaders?.() || {}
|
|
26
|
+
|
|
27
|
+
let optionHeaders: Record<string, string> = {}
|
|
28
|
+
|
|
29
|
+
if (options.headers instanceof Headers) {
|
|
30
|
+
options.headers.forEach((value, key) => {
|
|
31
|
+
optionHeaders[key] = value
|
|
32
|
+
})
|
|
33
|
+
} else if (Array.isArray(options.headers)) {
|
|
34
|
+
for (const [k, v] of options.headers) {
|
|
35
|
+
optionHeaders[k] = v
|
|
36
|
+
}
|
|
37
|
+
} else if (typeof options.headers === 'object' && options.headers !== null) {
|
|
38
|
+
optionHeaders = options.headers as Record<string, string>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const headers = {
|
|
42
|
+
...baseHeaders,
|
|
43
|
+
...optionHeaders
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return fetch(url, { ...options, headers })
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return client
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const BetterAuthProvider: AuthProvider = {
|
|
54
|
+
async login(credentials) {
|
|
55
|
+
const client = getClient()
|
|
56
|
+
const result = await client.signIn(credentials)
|
|
57
|
+
|
|
58
|
+
if (result.error) {
|
|
59
|
+
throw new Error(result.error.message || 'Login failed')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return result.data
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async logout() {
|
|
66
|
+
const client = getClient()
|
|
67
|
+
await client.signOut()
|
|
68
|
+
|
|
69
|
+
const ctx = getFrameworkContext()
|
|
70
|
+
ctx.reloadApp?.()
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
async session() {
|
|
74
|
+
const client = getClient()
|
|
75
|
+
const result = await client.session()
|
|
76
|
+
|
|
77
|
+
if (result.error) {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result.data
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
async register(data) {
|
|
85
|
+
const client = getClient()
|
|
86
|
+
const result = await client.signUp(data)
|
|
87
|
+
|
|
88
|
+
if (result.error) {
|
|
89
|
+
throw new Error(result.error.message || 'Registration failed')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return result.data
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
async refresh() {
|
|
96
|
+
const client = getClient()
|
|
97
|
+
const result = await client.refresh()
|
|
98
|
+
|
|
99
|
+
if (result.error) {
|
|
100
|
+
throw new Error(result.error.message || 'Session refresh failed')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return result.data
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
registerAuthProvider('better-auth', BetterAuthProvider)
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AuthProvider } from './types'
|
|
2
|
+
|
|
3
|
+
const providers: Record<string, AuthProvider> = {}
|
|
4
|
+
let activeProvider: string | null = null
|
|
5
|
+
|
|
6
|
+
export function registerAuthProvider(name: string, provider: AuthProvider) {
|
|
7
|
+
providers[name] = provider
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function setActiveAuthProvider(name: string) {
|
|
11
|
+
if (!providers[name]) {
|
|
12
|
+
throw new Error(`Auth provider "${name}" is not registered`)
|
|
13
|
+
}
|
|
14
|
+
activeProvider = name
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getAuthProvider(): AuthProvider {
|
|
18
|
+
if (!activeProvider) {
|
|
19
|
+
throw new Error('No active auth provider has been set')
|
|
20
|
+
}
|
|
21
|
+
return providers[activeProvider]
|
|
22
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
export interface AuthSession {
|
|
3
|
+
user: {
|
|
4
|
+
id: string
|
|
5
|
+
email?: string
|
|
6
|
+
name?: string
|
|
7
|
+
avatarUrl?: string
|
|
8
|
+
[key: string]: any
|
|
9
|
+
}
|
|
10
|
+
expiresAt?: string
|
|
11
|
+
[key: string]: any
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AuthCredentials {
|
|
15
|
+
email: string
|
|
16
|
+
password: string
|
|
17
|
+
[key: string]: any
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AuthRegistration {
|
|
21
|
+
email: string
|
|
22
|
+
password: string
|
|
23
|
+
name?: string
|
|
24
|
+
[key: string]: any
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AuthProvider {
|
|
28
|
+
login(credentials: AuthCredentials): Promise<AuthSession>
|
|
29
|
+
logout(): Promise<void>
|
|
30
|
+
session(): Promise<AuthSession | null>
|
|
31
|
+
register?(data: AuthRegistration): Promise<AuthSession>
|
|
32
|
+
refresh?(): Promise<AuthSession>
|
|
33
|
+
}
|
package/src/useAuth.ts
CHANGED
|
@@ -1,18 +1,112 @@
|
|
|
1
|
+
import type { Subscription } from '@better-auth/stripe'
|
|
2
|
+
import type { CustomerState } from '@polar-sh/sdk/models/components/customerstate.js'
|
|
3
|
+
import type {
|
|
4
|
+
BetterAuthClientOptions,
|
|
5
|
+
InferSessionFromClient,
|
|
6
|
+
User
|
|
7
|
+
} from 'better-auth/client'
|
|
1
8
|
import { createAuthClient } from 'better-auth/client'
|
|
2
|
-
import {
|
|
9
|
+
import { getAuthPlugins } from './plugins'
|
|
3
10
|
|
|
4
|
-
|
|
11
|
+
export type UseAuthOptions = {
|
|
12
|
+
/** An existing auth client. If provided, it's used as-is. */
|
|
13
|
+
client?: any
|
|
14
|
+
/** Base URL for the auth client if creating one. */
|
|
15
|
+
baseURL?: string
|
|
16
|
+
/** Optional headers to send with requests. */
|
|
17
|
+
headers?: Record<string, string>
|
|
18
|
+
/** Payment provider: 'stripe' or 'polar'. If omitted, 'stripe' is assumed. */
|
|
19
|
+
payment?: 'stripe' | 'polar'
|
|
20
|
+
/** Optional app-level reload/redirect function (framework-specific). */
|
|
21
|
+
reload?: (opts: { path?: string }) => Promise<void>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useAuth(options: UseAuthOptions = {}) {
|
|
25
|
+
const { client: providedClient, baseURL, headers, payment = 'stripe', reload } = options
|
|
26
|
+
|
|
27
|
+
const client = providedClient || createAuthClient({
|
|
28
|
+
baseURL: baseURL || undefined,
|
|
29
|
+
fetchOptions: {
|
|
30
|
+
headers
|
|
31
|
+
},
|
|
32
|
+
plugins: getAuthPlugins({ subscription: true })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
let session: InferSessionFromClient<BetterAuthClientOptions> | null = null
|
|
36
|
+
let user: User | null = null
|
|
37
|
+
let subscriptions: Subscription[] = []
|
|
38
|
+
let polarState: CustomerState | null = null
|
|
39
|
+
let sessionFetching = false
|
|
40
|
+
|
|
41
|
+
const fetchSession = async () => {
|
|
42
|
+
if (sessionFetching) return
|
|
43
|
+
sessionFetching = true
|
|
44
|
+
const { data } = await client.getSession()
|
|
45
|
+
session = data?.session || null
|
|
46
|
+
|
|
47
|
+
const userDefaults = {
|
|
48
|
+
image: null,
|
|
49
|
+
role: null,
|
|
50
|
+
banReason: null,
|
|
51
|
+
banned: null,
|
|
52
|
+
banExpires: null,
|
|
53
|
+
stripeCustomerId: null
|
|
54
|
+
}
|
|
55
|
+
user = data?.user ? Object.assign({}, userDefaults, data.user) : null
|
|
56
|
+
subscriptions = []
|
|
57
|
+
if (user) {
|
|
58
|
+
if (payment == 'stripe') {
|
|
59
|
+
const { data: subscriptionData } = await client.subscription.list()
|
|
60
|
+
subscriptions = subscriptionData || []
|
|
61
|
+
} else if (payment == 'polar') {
|
|
62
|
+
const { data: customerState } = await client.customer.state()
|
|
63
|
+
polarState = customerState
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
sessionFetching = false
|
|
67
|
+
return data
|
|
68
|
+
}
|
|
5
69
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
70
|
+
if (client?.$store?.listen) {
|
|
71
|
+
client.$store.listen('$sessionSignal', async (signal: any) => {
|
|
72
|
+
if (!signal) return
|
|
73
|
+
await fetchSession()
|
|
74
|
+
})
|
|
10
75
|
}
|
|
11
76
|
|
|
12
77
|
return {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
78
|
+
session,
|
|
79
|
+
user,
|
|
80
|
+
subscription: client.subscription,
|
|
81
|
+
subscriptions,
|
|
82
|
+
loggedIn: () => !!session,
|
|
83
|
+
activeStripeSubscription: () => {
|
|
84
|
+
return subscriptions.find((sub: { status: string }) => sub.status === 'active' || sub.status === 'trialing')
|
|
85
|
+
},
|
|
86
|
+
activePolarSubscriptions: () => {
|
|
87
|
+
return polarState?.activeSubscriptions
|
|
88
|
+
},
|
|
89
|
+
signIn: client.signIn,
|
|
90
|
+
signUp: client.signUp,
|
|
91
|
+
forgetPassword: client.forgetPassword,
|
|
92
|
+
resetPassword: client.resetPassword,
|
|
93
|
+
sendVerificationEmail: client.sendVerificationEmail,
|
|
94
|
+
errorCodes: client.$ERROR_CODES,
|
|
95
|
+
async signOut({ redirectTo }: { redirectTo?: string } = {}) {
|
|
96
|
+
await client.signOut({
|
|
97
|
+
fetchOptions: {
|
|
98
|
+
onSuccess: async () => {
|
|
99
|
+
session = null
|
|
100
|
+
user = null
|
|
101
|
+
if (redirectTo && typeof reload === 'function') {
|
|
102
|
+
await reload({ path: redirectTo.toString() })
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
},
|
|
108
|
+
fetchSession,
|
|
109
|
+
payment,
|
|
110
|
+
client
|
|
17
111
|
}
|
|
18
112
|
}
|