create new package for schemas and use it for auth
This commit is contained in:
parent
11827c30c1
commit
b68daf7312
@ -25,7 +25,8 @@
|
|||||||
"winston-loki": "^6.1.3",
|
"winston-loki": "^6.1.3",
|
||||||
"zod": "^3.25.42",
|
"zod": "^3.25.42",
|
||||||
"zod-validation-error": "^3.4.1",
|
"zod-validation-error": "^3.4.1",
|
||||||
"zod_utilz": "^0.8.4"
|
"zod_utilz": "^0.8.4",
|
||||||
|
"doorman-schema": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"twilio-run": "^3.5.4",
|
"twilio-run": "^3.5.4",
|
||||||
|
|||||||
@ -13,17 +13,8 @@ import { AuthMetrics, registerMetrics } from "../../../metrics/AuthMetrics";
|
|||||||
import { Counter } from "prom-client";
|
import { Counter } from "prom-client";
|
||||||
import { DoorConfig, getDoorConfigID } from "../../../schema/DoorConfig";
|
import { DoorConfig, getDoorConfigID } from "../../../schema/DoorConfig";
|
||||||
import { createLockStatusWithTimeout, getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
|
import { createLockStatusWithTimeout, getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
|
||||||
import { z } from "zod";
|
|
||||||
import { setResponseJson } from "../../../utils/responseUtils";
|
import { setResponseJson } from "../../../utils/responseUtils";
|
||||||
|
import { AuthRequest, AuthRequestSchema } from "../../../../../doorman-schema/src";
|
||||||
export const AuthRequestSchema = z.object({
|
|
||||||
door: z.string(),
|
|
||||||
key: z.string(),
|
|
||||||
ip: z.string().optional(),
|
|
||||||
timeout: z.coerce.number().gt(0, "Timeout cannot be 0").optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type AuthRequest = z.infer<typeof AuthRequestSchema>;
|
|
||||||
|
|
||||||
export interface AuthRequestTwilio extends ServerlessEventObject<AuthRequest, UserAgentHeader> { };
|
export interface AuthRequestTwilio extends ServerlessEventObject<AuthRequest, UserAgentHeader> { };
|
||||||
|
|
||||||
|
|||||||
9
packages/doorman-schema/package.json
Normal file
9
packages/doorman-schema/package.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "doorman-schema",
|
||||||
|
"module": "src/index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"zod": "^3.25.56",
|
||||||
|
"zod_utilz": "^0.8.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
packages/doorman-schema/src/index.ts
Normal file
1
packages/doorman-schema/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./routes/auth";
|
||||||
10
packages/doorman-schema/src/routes/auth.ts
Normal file
10
packages/doorman-schema/src/routes/auth.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export const AuthRequestSchema = z.object({
|
||||||
|
door: z.string(),
|
||||||
|
key: z.string().min(1, "PIN cannot be empty"),
|
||||||
|
ip: z.string().optional(),
|
||||||
|
timeout: z.coerce.number().gt(0, "Timeout cannot be 0").optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AuthRequest = z.infer<typeof AuthRequestSchema>;
|
||||||
@ -4,6 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudscape-design/components": "^3.0.375",
|
"@cloudscape-design/components": "^3.0.375",
|
||||||
|
"@hookform/resolvers": "^5.1.0",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^13.0.0",
|
"@testing-library/react": "^13.0.0",
|
||||||
"@testing-library/user-event": "^13.2.1",
|
"@testing-library/user-event": "^13.2.1",
|
||||||
@ -22,7 +23,8 @@
|
|||||||
"vite": "^4.4.9",
|
"vite": "^4.4.9",
|
||||||
"vite-plugin-svgr": "^4.0.0",
|
"vite-plugin-svgr": "^4.0.0",
|
||||||
"vite-tsconfig-paths": "^4.2.1",
|
"vite-tsconfig-paths": "^4.2.1",
|
||||||
"web-vitals": "^2.1.0"
|
"web-vitals": "^2.1.0",
|
||||||
|
"zod": "^3.25.56"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
import { Button, FormField, Icon, Input, SpaceBetween } from "@cloudscape-design/components";
|
import { Button, Form, FormField, Icon, Input, SpaceBetween } from "@cloudscape-design/components";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import type { AuthRequest } from "../../../doorman-api/src/functions/api/door/auth";
|
import { AuthRequest, AuthRequestSchema } from "../../../doorman-schema/src";
|
||||||
import { fetchUrlEncoded } from "../helpers/FetchHelper";
|
import { fetchUrlEncoded } from "../helpers/FetchHelper";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import CInput from "react-hook-form-cloudscape/components/input";
|
||||||
|
|
||||||
// COPIED FROM SERVER
|
// COPIED FROM SERVER
|
||||||
export enum IAuthMode {
|
export enum IAuthMode {
|
||||||
@ -10,7 +14,6 @@ export enum IAuthMode {
|
|||||||
RANDOM_ROTATING_KEY = "Random Rotating Key",
|
RANDOM_ROTATING_KEY = "Random Rotating Key",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IAuthComponentProps {
|
export interface IAuthComponentProps {
|
||||||
door: string,
|
door: string,
|
||||||
authMode: IAuthMode,
|
authMode: IAuthMode,
|
||||||
@ -21,17 +24,42 @@ export interface IAuthComponentProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AuthComponent = ({ door, secret, authMode, onError, onUnlock, runCheck }: IAuthComponentProps) => {
|
export const AuthComponent = ({ door, secret, authMode, onError, onUnlock, runCheck }: IAuthComponentProps) => {
|
||||||
const [ key, setKey ] = useState(secret || "");
|
const { control, setError, formState, handleSubmit, setValue, getValues } = useForm<AuthRequest>({
|
||||||
const [ error, setError ] = useState("");
|
resolver: zodResolver(AuthRequestSchema),
|
||||||
|
defaultValues: {
|
||||||
|
key: secret || "",
|
||||||
|
door,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [isLoading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const onSubmit = async (body: AuthRequest) => {
|
||||||
|
setLoading(true);
|
||||||
|
await fetchUrlEncoded('/api/door/auth', body)
|
||||||
|
.then(async res => {
|
||||||
|
if (res.status !== 200) {
|
||||||
|
setError("key", { type: "validate", message: "Incorrect PIN"});
|
||||||
|
onError && onError(await res.json());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onUnlock();
|
||||||
|
});
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (runCheck) {
|
// don't allow us to submit while we are submitting
|
||||||
onSubmit();
|
if (runCheck && !isLoading) {
|
||||||
|
handleSubmit(onSubmit)();
|
||||||
}
|
}
|
||||||
}, [runCheck])
|
}, [runCheck]);
|
||||||
|
|
||||||
const onSubmit = async () => {
|
|
||||||
const ip = await fetch('https://api.ipify.org?format=json')
|
// try and submit on load if the pin is passed in by query params
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(true);
|
||||||
|
fetch('https://api.ipify.org?format=json')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
return data.ip;
|
return data.ip;
|
||||||
@ -39,30 +67,24 @@ export const AuthComponent = ({ door, secret, authMode, onError, onUnlock, runCh
|
|||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.log('Error:', error);
|
console.log('Error:', error);
|
||||||
return "null";
|
return "null";
|
||||||
});
|
})
|
||||||
|
.then(async ip => {
|
||||||
|
setValue("ip", ip);
|
||||||
|
|
||||||
const body: AuthRequest = {
|
// if the key is there, then try submitting right away
|
||||||
door,
|
if (secret !== null) {
|
||||||
ip,
|
await handleSubmit(onSubmit)();
|
||||||
key,
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchUrlEncoded('/api/door/auth', body)
|
|
||||||
.then(async res => {
|
|
||||||
if (res.status !== 200) {
|
|
||||||
setError("Incorrect PIN");
|
|
||||||
onError && onError(await res.json());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
onUnlock();
|
|
||||||
});
|
setLoading(false);
|
||||||
}
|
});
|
||||||
|
}, [secret]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SpaceBetween size='l'>
|
<SpaceBetween size='l'>
|
||||||
<FormField errorText={error} label={"PIN"} constraintText={"Enter the PIN to unlock the door"}>
|
<FormField errorText={formState.errors.key?.message} label={"PIN"} constraintText={"Enter the PIN to unlock the door"}>
|
||||||
<Input readOnly={secret !== null} value={key || ""} onChange={({ detail }) => setKey(detail.value)} />
|
<CInput type="password" name="key" readOnly={secret !== null} control={control} />
|
||||||
</FormField>
|
</FormField>
|
||||||
</SpaceBetween>
|
</SpaceBetween>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -36,7 +36,5 @@ const router = createBrowserRouter([
|
|||||||
])
|
])
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<RouterProvider router={router} />
|
||||||
<RouterProvider router={router} />
|
|
||||||
</React.StrictMode>
|
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user