From 0fc092cde777c28ffafe03ccb122c1b1da648400 Mon Sep 17 00:00:00 2001
From: musordmt
Date: Thu, 11 Jul 2024 22:19:05 +0300
Subject: [PATCH 1/3] share auth info
---
.../auth/components/email-password-form.tsx | 6 +-
packages/auth/components/index.ts | 3 +-
packages/auth/components/login-panel.tsx | 63 +++---
packages/auth/components/signup-panel.tsx | 184 ++++++++++++++++++
packages/auth/service/auth-service.ts | 3 +-
.../auth/service/impl/firebase-support.ts | 125 ++++++++----
packages/auth/service/impl/index.ts | 45 ++++-
7 files changed, 353 insertions(+), 76 deletions(-)
create mode 100644 packages/auth/components/signup-panel.tsx
diff --git a/packages/auth/components/email-password-form.tsx b/packages/auth/components/email-password-form.tsx
index 8ef98192..d95bf0ae 100644
--- a/packages/auth/components/email-password-form.tsx
+++ b/packages/auth/components/email-password-form.tsx
@@ -27,11 +27,13 @@ const EmailPasswordForm: React.FC<{
isLoading: boolean
className?: string
inputClx?: string
+ content?: string
}> = ({
onSubmit,
isLoading,
className,
- inputClx
+ inputClx,
+ content='Login'
}) => {
const form = useForm>({
resolver: zodResolver(formSchema),
@@ -82,7 +84,7 @@ const EmailPasswordForm: React.FC<{
)}
/>
-
+
)
diff --git a/packages/auth/components/index.ts b/packages/auth/components/index.ts
index dc3cb92e..a92e1b45 100644
--- a/packages/auth/components/index.ts
+++ b/packages/auth/components/index.ts
@@ -1,3 +1,4 @@
export { default as LoginPanel } from './login-panel'
export { default as EmailPasswordForm } from './email-password-form'
-export { default as AuthWidget } from './auth-widget'
\ No newline at end of file
+export { default as AuthWidget } from './auth-widget'
+export { default as SignupPanel } from './signup-panel'
\ No newline at end of file
diff --git a/packages/auth/components/login-panel.tsx b/packages/auth/components/login-panel.tsx
index 8173e1d6..3dd368cc 100644
--- a/packages/auth/components/login-panel.tsx
+++ b/packages/auth/components/login-panel.tsx
@@ -4,7 +4,7 @@ import { useRouter } from 'next/navigation'
import { observer } from 'mobx-react-lite'
import Link from 'next/link'
-import { ApplyTypography, Button, Separator, toast } from '@hanzo/ui/primitives'
+import { ApplyTypography, Button, Separator, toast, Toaster } from '@hanzo/ui/primitives'
import { cn } from '@hanzo/ui/util'
import { useAuth, type AuthProvider } from '../service'
@@ -23,18 +23,18 @@ const ProviderLoginButton: React.FC {
-
- return (
-
- )
-}
+
+ return (
+
+ )
+ }
const LoginPanel: React.FC void
termsOfServiceUrl?: string
privacyPolicyUrl?: string
+ setIsLogin?: React.Dispatch>
}> = observer(({
children,
redirectUrl,
@@ -54,21 +55,22 @@ const LoginPanel: React.FC {
const router = useRouter()
const auth = useAuth()
const [isLoading, setIsLoading] = useState(false)
- const succeed = async (loginMethod: AuthProvider | 'email' | null ) => {
+ const succeed = async (loginMethod: AuthProvider | 'email' | null) => {
if (loginMethod) {
sendGAEvent('login', { method: loginMethod })
}
- // If a callback is provided, don't redirect.
- // Assume host code is handling (eg, mobile menu)
+ // If a callback is provided, don't redirect.
+ // Assume host code is handling (eg, mobile menu)
if (onLoginChanged) {
const res = await fetch(
'/api/auth/generate-custom-token',
@@ -77,7 +79,7 @@ const LoginPanel: React.FC {
+ if (setIsLogin) setIsLogin(false)
+ }
+
return (
{auth.loggedIn && !redirectUrl ? (
@@ -143,25 +150,31 @@ const LoginPanel: React.FCLogin
)}
{children}
-
+
+
+
+
Don't have an account?
+
+
-
or continue with
+
or continue with
{/* */}
- Google
+ Google
- Facebook
+ Facebook
- Github
+ Github
By logging in, you agree to our Terms of Service and Privacy Policy.
+
>
)}
diff --git a/packages/auth/components/signup-panel.tsx b/packages/auth/components/signup-panel.tsx
new file mode 100644
index 00000000..581e802a
--- /dev/null
+++ b/packages/auth/components/signup-panel.tsx
@@ -0,0 +1,184 @@
+'use client'
+import { useState, type PropsWithChildren } from 'react'
+import { useRouter } from 'next/navigation'
+import { observer } from 'mobx-react-lite'
+import Link from 'next/link'
+
+import { ApplyTypography, Button, Separator, toast, Toaster } from '@hanzo/ui/primitives'
+import { cn } from '@hanzo/ui/util'
+
+import { useAuth, type AuthProvider } from '../service'
+import { Facebook, Google, GitHub } from '../icons'
+import EmailPasswordForm from './email-password-form'
+import { sendGAEvent } from '../util/analytics'
+
+
+const ProviderLoginButton: React.FC Promise,
+ isLoading: boolean
+}> = ({
+ provider,
+ loginWithProvider,
+ isLoading,
+ children
+}) => {
+
+ return (
+
+ )
+ }
+
+const SignupPanel: React.FC void
+ termsOfServiceUrl?: string
+ privacyPolicyUrl?: string
+ setIsLogin?: React.Dispatch>
+}> = observer(({
+ children,
+ redirectUrl,
+ getStartedUrl,
+ className,
+ inputClx,
+ noHeading,
+ onLoginChanged,
+ termsOfServiceUrl,
+ privacyPolicyUrl,
+ setIsLogin
+}) => {
+
+ const router = useRouter()
+ const auth = useAuth()
+ const [isLoading, setIsLoading] = useState(false)
+
+ const succeed = async (loginMethod: AuthProvider | 'email' | null) => {
+
+ if (loginMethod) {
+ sendGAEvent('login', { method: loginMethod })
+ }
+
+ // If a callback is provided, don't redirect.
+ // Assume host code is handling (eg, mobile menu)
+ if (onLoginChanged) {
+ const res = await fetch(
+ '/api/auth/generate-custom-token',
+ { method: 'POST' }
+ ).then(res => res.json())
+ onLoginChanged(res.token?.token ?? null)
+ }
+ else if (redirectUrl) {
+ // TODO :aa shouldn't the token thing happen in this case too??
+ router.push(redirectUrl)
+ }
+ }
+
+ const signupWithEmailPassword = async (email: string, password: string) => {
+ setIsLoading(true)
+ try {
+ const res = await auth.signupEmailAndPassword(email, password)
+ if (res.success) { succeed('email'); toast.success(res.message) }
+ else { toast.error(res.message) }
+ }
+ catch (e) {
+ toast.success('User with this email already signed up')
+ }
+ setIsLoading(false)
+ }
+
+ // const loginWithEthereum = async () => {
+ // setIsLoading(true)
+ // try {
+ // const res = await signInWithEthereum()
+ // if (res.success && res.user) {
+ // setUser({email: res.user?.email, displayName: res.user?.displayName, walletAddress: res.user?.walletAddress})
+ // if (redirectUrl) {
+ // router.push(redirectUrl)
+ // }
+ // }
+ // } catch (e) {
+ // toast({title: 'No Ethereum provider found'})
+ // }
+ // setIsLoading(false)
+ // }
+
+ const loginWithProvider = async (provider: AuthProvider) => {
+ setIsLoading(true)
+ const res = await auth.loginWithProvider(provider)
+ if (res.success) { succeed(provider) }
+ setIsLoading(false)
+ }
+
+ const logout = async () => {
+ setIsLoading(true)
+ const res = await auth.logout()
+ if (res.success) { succeed(null) }
+ setIsLoading(false)
+ }
+
+ const handleOnClick = () => {
+ if (setIsLogin) setIsLogin(true)
+ }
+
+ return (
+
+ {auth.loggedIn && !redirectUrl ? (
+ <>
+ Welcome!
+ {auth.user && (<> {/* this means the hanzo user isn't loaded yet ...*/}
+ You are signed in as {auth.user?.displayName ?? auth.user?.email}
+
+ {getStartedUrl && }
+
+
+ >)}
+ >
+ ) : (
+ <>
+ {!noHeading && (
+ SignUp
+ )}
+ {children}
+
+
+
+
Already have account?
+
+
+
+
+
+ {/* */}
+
+ Google
+
+
+ Facebook
+
+
+ Github
+
+ By logging in, you agree to our Terms of Service and Privacy Policy.
+
+ >
+ )}
+
+ )
+})
+
+export default SignupPanel
diff --git a/packages/auth/service/auth-service.ts b/packages/auth/service/auth-service.ts
index 3f2c83fa..bfc5a7d2 100644
--- a/packages/auth/service/auth-service.ts
+++ b/packages/auth/service/auth-service.ts
@@ -7,7 +7,8 @@ interface AuthService {
get loggedIn(): boolean
get user(): HanzoUserInfo | null // returns current info obj // all fields observable :)
- loginEmailAndPassword: ( email: string, password: string ) => Promise<{success: boolean, userInfo: HanzoUserInfo | null}>
+ signupEmailAndPassword: ( email: string, password: string ) => Promise<{success: boolean, userInfo: HanzoUserInfo | null, message?: string}>
+ loginEmailAndPassword: ( email: string, password: string ) => Promise<{success: boolean, userInfo: HanzoUserInfo | null, message?: string}>
loginWithProvider: ( provider: AuthProvider ) => Promise<{success: boolean, userInfo: HanzoUserInfo | null}>
loginWithCustomToken: ( token: string ) => Promise<{success: boolean, userInfo: HanzoUserInfo | null}>
associateWallet: () => Promise
diff --git a/packages/auth/service/impl/firebase-support.ts b/packages/auth/service/impl/firebase-support.ts
index bf877ff3..78f86c56 100644
--- a/packages/auth/service/impl/firebase-support.ts
+++ b/packages/auth/service/impl/firebase-support.ts
@@ -10,7 +10,7 @@ import {
signInWithCustomToken,
} from 'firebase/auth'
-import { initializeApp, getApps } from "firebase/app"
+import { initializeApp, getApps, FirebaseError } from "firebase/app"
import { getAuth } from "firebase/auth"
import { getFirestore } from 'firebase/firestore'
@@ -27,10 +27,10 @@ export const firebaseConfig = {
const firebaseApp = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0]
export const auth = getAuth(firebaseApp)
- // :aa TODO should be in module conf in host app
-export const db = getFirestore(firebaseApp, 'lux-accounts')
+// :aa TODO should be in module conf in host app
+export const db = getFirestore(firebaseApp, 'lux-accounts')
-export async function loginWithProvider(provider: string): Promise<{success: boolean, user: User | null}> {
+export async function loginWithProvider(provider: string): Promise<{ success: boolean, user: User | null }> {
const authProvider = (() => {
switch (provider) {
case 'google':
@@ -45,7 +45,7 @@ export async function loginWithProvider(provider: string): Promise<{success: boo
})()
if (!authProvider) {
- return {success: false, user: null}
+ return { success: false, user: null }
}
try {
@@ -60,16 +60,16 @@ export async function loginWithProvider(provider: string): Promise<{success: boo
const resBody = (await response.json()) as unknown as APIResponse
if (response.ok && resBody.success) {
-// const walletAddress = await getAssociatedWalletAddress(userCreds.user.email ?? '')
- return {success: true, user: userCreds.user /*.email ? {email: userCreds.user.email, displayName: userCreds.user.displayName ?? undefined, walletAddress: walletAddress.result}: null */}
- }
+ // const walletAddress = await getAssociatedWalletAddress(userCreds.user.email ?? '')
+ return { success: true, user: userCreds.user /*.email ? {email: userCreds.user.email, displayName: userCreds.user.displayName ?? undefined, walletAddress: walletAddress.result}: null */ }
+ }
else {
- return {success: false, user: null}
+ return { success: false, user: null }
}
- }
+ }
catch (error) {
console.error('Error signing in with Google', error)
- return {success: false, user: null}
+ return { success: false, user: null }
}
}
@@ -93,29 +93,68 @@ export async function signInWithEthereum(opts?: { siteName?: string }): Promise<
}
*/
const isAuthUserNotFound = (e: any) => (
- typeof e === 'object' &&
- e !== null &&
- e.hasOwnProperty('code') &&
+ typeof e === 'object' &&
+ e !== null &&
+ e.hasOwnProperty('code') &&
e.code === 'auth/user-not-found'
)
+export async function signupWithEmailAndPassword(
+ email: string,
+ password: string
+): Promise<{ success: boolean, user?: User, message?: string }> {
+
+ let user: User | undefined = undefined
+ try {
+ const userCredential = await createUserWithEmailAndPassword(auth, email, password)
+ user = userCredential.user
+ }
+ catch (error) {
+ if (error instanceof FirebaseError) {
+ console.error(error.code)
+ return {success: false, message: error.code as string}
+ }
+ return {success: false, message: error as string}
+ }
+
+ try {
+ const idToken = await user.getIdToken()
+
+ const response = await fetch('/api/auth/login', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ idToken }),
+ })
+ const resBody = (await response.json()) as unknown as APIResponse
+
+ if (response.ok && resBody.success) {
+ return { success: true, user }
+ }
+ else {
+ return { success: false }
+ }
+ }
+ catch (error) {
+ console.error('Error signing in with Firebase auth', error)
+ return { success: false }
+ }
+}
+
export async function loginWithEmailAndPassword(
- email: string,
+ email: string,
password: string
-): Promise<{success: boolean, user?: User }> {
+): Promise<{ success: boolean, user?: User, message?: string }> {
let user: User | undefined = undefined
try {
const userCredential = await signInWithEmailAndPassword(auth, email, password)
user = userCredential.user
- } catch (e) {
- if (isAuthUserNotFound(e)) {
- const userCredential = await createUserWithEmailAndPassword(auth, email, password)
- user = userCredential.user
- }
- else {
- throw e
+ } catch (error) {
+ if (error instanceof FirebaseError) {
+ console.error(error.code)
+ return {success: false, message: error.code as string}
}
+ return {success: false, message: error as string}
}
try {
@@ -123,27 +162,27 @@ export async function loginWithEmailAndPassword(
const response = await fetch('/api/auth/login', {
method: 'POST',
- headers: { 'Content-Type': 'application/json'},
+ headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ idToken }),
})
const resBody = (await response.json()) as unknown as APIResponse
if (response.ok && resBody.success) {
- return { success: true, user }
- }
+ return { success: true, user, message: "Login Successfully!" }
+ }
else {
- return {success: false}
+ return { success: false , message: "Login API Failed"}
}
- }
+ }
catch (error) {
console.error('Error signing in with Firebase auth', error)
- return {success: false}
+ return { success: false, message: "Error signing in with Firebase auth" }
}
}
export async function loginWithCustomToken(
- token: string,
-): Promise<{success: boolean, user?: User }> {
+ token: string,
+): Promise<{ success: boolean, user?: User }> {
let user: User | undefined = undefined
const userCredential = await signInWithCustomToken(auth, token)
@@ -154,40 +193,40 @@ export async function loginWithCustomToken(
const response = await fetch('/api/auth/login', {
method: 'POST',
- headers: { 'Content-Type': 'application/json'},
+ headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ idToken }),
})
const resBody = (await response.json()) as unknown as APIResponse
if (response.ok && resBody.success) {
return { success: true, user }
- }
+ }
else {
- return {success: false}
+ return { success: false }
}
- }
+ }
catch (error) {
console.error('Error signing in with Firebase auth', error)
- return {success: false}
+ return { success: false }
}
}
-export async function logoutBackend(): Promise<{success: boolean}> {
+export async function logoutBackend(): Promise<{ success: boolean }> {
try {
- const response = await fetch('/api/auth/logout', { headers: {'Content-Type': 'application/json' } })
+ const response = await fetch('/api/auth/logout', { headers: { 'Content-Type': 'application/json' } })
const resBody = (await response.json()) as unknown as APIResponse
if (response.ok && resBody.success) {
- return {success: true}
- }
+ return { success: true }
+ }
else {
- return {success: false}
+ return { success: false }
}
- }
+ }
catch (error) {
console.error('Error logging on on server with Firebase', error)
- return {success: false}
+ return { success: false }
}
}
diff --git a/packages/auth/service/impl/index.ts b/packages/auth/service/impl/index.ts
index 2e43b8bc..bd2418fd 100644
--- a/packages/auth/service/impl/index.ts
+++ b/packages/auth/service/impl/index.ts
@@ -5,6 +5,7 @@ import type { AuthServiceConf, HanzoUserInfo, HanzoUserInfoValue } from '../../t
import {
auth as fbAuth,
+ signupWithEmailAndPassword,
loginWithCustomToken,
loginWithEmailAndPassword,
loginWithProvider,
@@ -71,10 +72,44 @@ class AuthServiceImpl implements AuthService {
)
}
+ signupEmailAndPassword = async (
+ email: string,
+ password: string
+ ): Promise<{success: boolean, userInfo: HanzoUserInfo | null, message?: string}> => {
+
+ try {
+ this._hzUser.clear()
+ const res = await signupWithEmailAndPassword(email, password)
+ if (res.success && res.user) {
+ const walletAddress = res.user.email ? await getAssociatedWalletAddress(res.user.email) : undefined
+ this._hzUser.set({
+ email: res.user.email ?? '',
+ displayName : res.user.displayName ?? null,
+ walletAddress : walletAddress?.result ?? null
+ })
+
+ return {
+ success: true,
+ userInfo: this._hzUser,
+ message: res.message
+ }
+ }
+ return {
+ success: false,
+ userInfo: null,
+ message: res.message
+ }
+ }
+ catch (e) {
+ console.error('Error signing in with Firebase auth', e)
+ return {success: false, userInfo: null, message: 'Error signing in with Firebase auth'}
+ }
+ }
+
loginEmailAndPassword = async (
email: string,
password: string
- ): Promise<{success: boolean, userInfo: HanzoUserInfo | null}> => {
+ ): Promise<{success: boolean, userInfo: HanzoUserInfo | null, message?: string}> => {
try {
this._hzUser.clear()
@@ -89,17 +124,19 @@ class AuthServiceImpl implements AuthService {
return {
success: true,
- userInfo: this._hzUser
+ userInfo: this._hzUser,
+ message: res.message
}
}
return {
success: false,
- userInfo: null
+ userInfo: null,
+ message: res.message
}
}
catch (e) {
console.error('Error signing in with Firebase auth', e)
- return {success: false, userInfo: null}
+ return {success: false, userInfo: null, message: 'Error signing in with Firebase auth'}
}
}
From 79a968de9e02925f40464a29602acca6c54893ce Mon Sep 17 00:00:00 2001
From: ZooSOS
Date: Fri, 12 Jul 2024 12:37:14 -0700
Subject: [PATCH 2/3] auth version upgrade
---
packages/auth/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/auth/package.json b/packages/auth/package.json
index 6218c935..9bbec5a5 100644
--- a/packages/auth/package.json
+++ b/packages/auth/package.json
@@ -1,6 +1,6 @@
{
"name": "@hanzo/auth",
- "version": "2.4.12",
+ "version": "2.4.14",
"description": "Library with Firebase authentication.",
"publishConfig": {
"registry": "https://registry.npmjs.org/",
From a374eaa1eeca5c0965de699e0d81a99409094f92 Mon Sep 17 00:00:00 2001
From: artem ash
Date: Thu, 11 Jul 2024 22:34:50 -0700
Subject: [PATCH 3/3] PR Review
---
.../auth/components/email-password-form.tsx | 6 +--
packages/auth/components/login-panel.tsx | 30 ++++++++----
packages/auth/components/signup-panel.tsx | 48 ++++++++++++-------
3 files changed, 55 insertions(+), 29 deletions(-)
diff --git a/packages/auth/components/email-password-form.tsx b/packages/auth/components/email-password-form.tsx
index d95bf0ae..c651f21e 100644
--- a/packages/auth/components/email-password-form.tsx
+++ b/packages/auth/components/email-password-form.tsx
@@ -27,13 +27,13 @@ const EmailPasswordForm: React.FC<{
isLoading: boolean
className?: string
inputClx?: string
- content?: string
+ prompt?: string
}> = ({
onSubmit,
isLoading,
className,
inputClx,
- content='Login'
+ prompt='Login'
}) => {
const form = useForm>({
resolver: zodResolver(formSchema),
@@ -84,7 +84,7 @@ const EmailPasswordForm: React.FC<{
)}
/>
-
+
)
diff --git a/packages/auth/components/login-panel.tsx b/packages/auth/components/login-panel.tsx
index 3dd368cc..1abba6bc 100644
--- a/packages/auth/components/login-panel.tsx
+++ b/packages/auth/components/login-panel.tsx
@@ -26,7 +26,7 @@ const ProviderLoginButton: React.FC loginWithProvider(provider)}
+ onClick={() => {loginWithProvider(provider)}}
className='w-full mx-auto flex items-center gap-2'
disabled={isLoading}
variant='outline'
@@ -69,8 +69,8 @@ const LoginPanel: React.FC {
setIsLoading(true)
const res = await auth.loginWithProvider(provider)
- if (res.success) { succeed(provider) }
+ if (res.success) {
+ succeed(provider)
+ }
setIsLoading(false)
}
const logout = async () => {
setIsLoading(true)
const res = await auth.logout()
- if (res.success) { succeed(null) }
+ if (res.success) {
+ succeed(null)
+ }
setIsLoading(false)
}
const handleOnClick = () => {
- if (setIsLogin) setIsLogin(false)
+ if (setIsLogin) {
+ setIsLogin(false)
+ }
}
return (
@@ -140,7 +150,7 @@ const LoginPanel: React.FCYou are signed in as {auth.user?.displayName ?? auth.user?.email}
{getStartedUrl && }
-
+
>)}
>
diff --git a/packages/auth/components/signup-panel.tsx b/packages/auth/components/signup-panel.tsx
index 581e802a..d6c77b2f 100644
--- a/packages/auth/components/signup-panel.tsx
+++ b/packages/auth/components/signup-panel.tsx
@@ -69,8 +69,8 @@ const SignupPanel: React.FC {
setIsLoading(true)
const res = await auth.loginWithProvider(provider)
- if (res.success) { succeed(provider) }
+ if (res.success) {
+ succeed(provider)
+ }
setIsLoading(false)
}
const logout = async () => {
setIsLoading(true)
const res = await auth.logout()
- if (res.success) { succeed(null) }
+ if (res.success) {
+ succeed(null)
+ }
setIsLoading(false)
}
const handleOnClick = () => {
- if (setIsLogin) setIsLogin(true)
+ if (setIsLogin) {
+ setIsLogin(true)
+ }
}
return (
-
+
{auth.loggedIn && !redirectUrl ? (
<>
Welcome!
{auth.user && (<> {/* this means the hanzo user isn't loaded yet ...*/}
You are signed in as {auth.user?.displayName ?? auth.user?.email}
- {getStartedUrl && }
-
+ {getStartedUrl && }
+
>)}
>
) : (
<>
{!noHeading && (
- SignUp
+ Sign Up
)}
{children}
-
-
+
-
Already have account?
+
Already have an account?
@@ -174,7 +190,7 @@ const SignupPanel: React.FCGithub
By logging in, you agree to our Terms of Service and Privacy Policy.
-
+
>
)}