@rpcbase/auth 0.0.0
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/package.json +29 -0
- package/src/api/sign-in/handler.ts +53 -0
- package/src/api/sign-in/index.ts +28 -0
- package/src/api/sign-out/handler.ts +17 -0
- package/src/api/sign-out/index.ts +14 -0
- package/src/components/AppleSignInButton/index.tsx +36 -0
- package/src/components/EmailOrPhoneInput/index.tsx +32 -0
- package/src/components/RememberMeCheckbox/index.tsx +27 -0
- package/src/components/SignInForm/index.tsx +42 -0
- package/src/components/index.ts +4 -0
- package/src/index.ts +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rpcbase/auth",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc --watch",
|
|
8
|
+
"release": "wireit"
|
|
9
|
+
},
|
|
10
|
+
"wireit": {
|
|
11
|
+
"release": {
|
|
12
|
+
"command": "../../scripts/publish.js",
|
|
13
|
+
"dependencies": [],
|
|
14
|
+
"files": [
|
|
15
|
+
"package.json"
|
|
16
|
+
],
|
|
17
|
+
"output": [],
|
|
18
|
+
"env": {
|
|
19
|
+
"NPM_RELEASE_CHANNEL": {
|
|
20
|
+
"external": true
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@rpcbase/ui": "0.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {}
|
|
29
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// import crypto from "crypto"
|
|
2
|
+
import isEmail from "validator/lib/isEmail"
|
|
3
|
+
|
|
4
|
+
import {Api, loadModel, Ctx, ApiHandler} from "@rpcbase/api"
|
|
5
|
+
import { hashPassword } from "@rpcbase/server"
|
|
6
|
+
|
|
7
|
+
import * as SignIn from "./index"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const signIn = async(payload: SignIn.RequestPayload, ctx: Ctx): Promise<SignIn.ResponsePayload> => {
|
|
11
|
+
const User = await loadModel("User", ctx)
|
|
12
|
+
|
|
13
|
+
const {email_or_phone} = SignIn.requestSchema.parse(payload)
|
|
14
|
+
|
|
15
|
+
if (isEmail(email_or_phone)) {
|
|
16
|
+
console.log("is valid email")
|
|
17
|
+
} else {
|
|
18
|
+
console.log("is not valid email")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {success: true}
|
|
22
|
+
|
|
23
|
+
// console.log("will lookup user", email)
|
|
24
|
+
// const user = await User.findOne({email})
|
|
25
|
+
|
|
26
|
+
// if (!user) {
|
|
27
|
+
// console.log("user not found", email)
|
|
28
|
+
// return { success: false }
|
|
29
|
+
// }
|
|
30
|
+
|
|
31
|
+
// console.log("found user", user._id.toString())
|
|
32
|
+
|
|
33
|
+
// console.time("checkPassword")
|
|
34
|
+
// const [salt, hashedPassword] = user.password.split(":")
|
|
35
|
+
// const derivedKey = await hashPassword(password, salt)
|
|
36
|
+
// const passwordMatches = crypto.timingSafeEqual(Buffer.from(hashedPassword, "hex"), derivedKey)
|
|
37
|
+
// console.timeEnd("checkPassword")
|
|
38
|
+
|
|
39
|
+
// ctx.req.session.user = {
|
|
40
|
+
// id: user._id.toString(),
|
|
41
|
+
// current_tenant_id: user.tenants[0],
|
|
42
|
+
// // TODO: handle multiple tenants
|
|
43
|
+
// signed_in_tenants: [user.tenants[0]],
|
|
44
|
+
// }
|
|
45
|
+
|
|
46
|
+
// return {
|
|
47
|
+
// success: passwordMatches
|
|
48
|
+
// }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default (api: Api) => {
|
|
52
|
+
api.post(SignIn.Route, signIn as ApiHandler)
|
|
53
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import { isValidNumber } from "libphonenumber-js"
|
|
3
|
+
|
|
4
|
+
export const Route = "/api/rb/auth/sign-in"
|
|
5
|
+
|
|
6
|
+
export const requestSchema = z.object({
|
|
7
|
+
email_or_phone: z
|
|
8
|
+
.string()
|
|
9
|
+
.nonempty("Email or phone number is required")
|
|
10
|
+
.default("")
|
|
11
|
+
.refine(
|
|
12
|
+
(value) => {
|
|
13
|
+
const isEmail = z.string().email().safeParse(value).success
|
|
14
|
+
const isPhone = isValidNumber(value)
|
|
15
|
+
return isEmail || isPhone
|
|
16
|
+
},
|
|
17
|
+
"Please enter a valid email address or phone number"
|
|
18
|
+
),
|
|
19
|
+
remember_me: z.boolean().default(true),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export type RequestPayload = z.infer<typeof requestSchema>
|
|
23
|
+
|
|
24
|
+
export const responseSchema = z.object({
|
|
25
|
+
success: z.boolean(),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export type ResponsePayload = z.infer<typeof responseSchema>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Api, Ctx, ApiHandler } from "@rpcbase/api"
|
|
2
|
+
|
|
3
|
+
import * as SignOut from "./index"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const handleSignOut = async(_: SignOut.RequestPayload, ctx: Ctx): Promise<SignOut.ResponsePayload> => {
|
|
7
|
+
// Destroy the session
|
|
8
|
+
await new Promise<void>((resolve) => ctx.req.session.destroy(() => resolve()))
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
success: true
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default (api: Api) => {
|
|
16
|
+
api.post(SignOut.Route, handleSignOut as ApiHandler)
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
|
|
3
|
+
export const Route = "/api/rb/auth/sign-out"
|
|
4
|
+
|
|
5
|
+
// No request payload needed for signout
|
|
6
|
+
export const requestSchema = z.object({})
|
|
7
|
+
|
|
8
|
+
export type RequestPayload = z.infer<typeof requestSchema>;
|
|
9
|
+
|
|
10
|
+
export const responseSchema = z.object({
|
|
11
|
+
success: z.boolean()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export type ResponsePayload = z.infer<typeof responseSchema>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import clsx from "clsx"
|
|
2
|
+
|
|
3
|
+
export const AppleSignInButton = ({
|
|
4
|
+
onPress,
|
|
5
|
+
className,
|
|
6
|
+
}: {
|
|
7
|
+
onPress: () => void;
|
|
8
|
+
className?: string;
|
|
9
|
+
}) => {
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<button
|
|
13
|
+
onClick={onPress}
|
|
14
|
+
className={clsx(`
|
|
15
|
+
w-full
|
|
16
|
+
bg-black text-white hover:bg-gray-800
|
|
17
|
+
flex items-center justify-center
|
|
18
|
+
px-6 py-2
|
|
19
|
+
rounded-lg
|
|
20
|
+
font-medium
|
|
21
|
+
transition duration-150 ease-in-out
|
|
22
|
+
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black
|
|
23
|
+
`, className)}
|
|
24
|
+
>
|
|
25
|
+
<svg
|
|
26
|
+
className="w-5 h-5 mr-3"
|
|
27
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
28
|
+
viewBox="0 0 24 24"
|
|
29
|
+
fill="white"
|
|
30
|
+
>
|
|
31
|
+
<path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.539 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701" />
|
|
32
|
+
</svg>
|
|
33
|
+
Continuer avec Apple
|
|
34
|
+
</button>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {useFormContext} from "@rpcbase/form"
|
|
2
|
+
|
|
3
|
+
export const EmailOrPhoneInput = ({
|
|
4
|
+
id,
|
|
5
|
+
className,
|
|
6
|
+
placeholder,
|
|
7
|
+
}: {
|
|
8
|
+
id: string
|
|
9
|
+
className?: string
|
|
10
|
+
placeholder?: string
|
|
11
|
+
}) => {
|
|
12
|
+
const {register, formState} = useFormContext()
|
|
13
|
+
|
|
14
|
+
const error = formState.errors.email_or_phone
|
|
15
|
+
|
|
16
|
+
// console.log("ERRR", errors)
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<input
|
|
21
|
+
id={id}
|
|
22
|
+
type="text"
|
|
23
|
+
required
|
|
24
|
+
autoComplete="on"
|
|
25
|
+
className={className}
|
|
26
|
+
placeholder={placeholder}
|
|
27
|
+
{...register("email_or_phone")}
|
|
28
|
+
/>
|
|
29
|
+
{error && <p className="mt-1 -mb-2 text-sm/6 text-red-500">{error.message}</p>}
|
|
30
|
+
</>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {ComponentType} from "react"
|
|
2
|
+
import {useFormContext} from "@rpcbase/form"
|
|
3
|
+
|
|
4
|
+
export const RememberMeCheckbox = ({
|
|
5
|
+
label,
|
|
6
|
+
as: Component = "input"
|
|
7
|
+
}: {
|
|
8
|
+
label: string,
|
|
9
|
+
as?: ComponentType<any> | string
|
|
10
|
+
}) => {
|
|
11
|
+
const {register, formState} = useFormContext()
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<>
|
|
15
|
+
<Component
|
|
16
|
+
id="remember_me"
|
|
17
|
+
{...register("remember_me")}
|
|
18
|
+
/>
|
|
19
|
+
<label
|
|
20
|
+
htmlFor={"remember_me"}
|
|
21
|
+
className="pl-3 block text-sm/6 text-gray-900"
|
|
22
|
+
>
|
|
23
|
+
{label}
|
|
24
|
+
</label>
|
|
25
|
+
</>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {ReactNode, useEffect} from "react"
|
|
2
|
+
import { useForm, FormProvider, zodResolver } from "@rpcbase/form"
|
|
3
|
+
|
|
4
|
+
import {requestSchema, RequestPayload} from "../../api/sign-in"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export const SignInForm = ({
|
|
8
|
+
children,
|
|
9
|
+
className
|
|
10
|
+
}: {
|
|
11
|
+
children: ReactNode,
|
|
12
|
+
className?: string
|
|
13
|
+
}) => {
|
|
14
|
+
|
|
15
|
+
const methods = useForm<RequestPayload>({
|
|
16
|
+
defaultValues: {
|
|
17
|
+
email_or_phone: "",
|
|
18
|
+
remember_me: true,
|
|
19
|
+
},
|
|
20
|
+
resolver: zodResolver(requestSchema)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const onSubmit = async(data: RequestPayload, event) => {
|
|
24
|
+
console.log("SUBMIT SIGNIN", data)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
console.log(methods.formState.errors)
|
|
29
|
+
}, [methods.formState.errors])
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
// console.log("FOR5M", methods.formState.values)
|
|
33
|
+
}, [methods.formState.values])
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<FormProvider {...methods}>
|
|
37
|
+
<form method="post" className={className} onSubmit={methods.handleSubmit(onSubmit)}>
|
|
38
|
+
{children}
|
|
39
|
+
</form>
|
|
40
|
+
</FormProvider>
|
|
41
|
+
)
|
|
42
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./components"
|