interim
This commit is contained in:
@@ -77,11 +77,55 @@ API_ROOT = "http://localhost:3000"
|
||||
GOOGLE_MAP_API_KEY = AIzaSyCzeVrHt544bp_72YCFkw21eDsmI2JEsQo
|
||||
|
||||
|
||||
EMAIL_SERVER_HOST=smtp.livemail.co.uk
|
||||
//EMAIL_SERVER_HOST=smtp.livemail.co.uk
|
||||
//EMAIL_SERVER_PORT=465
|
||||
//EMAIL_SERVER_USER=rob@rdbsolutions.co.uk
|
||||
//EMAIL_SERVER_PASSWORD=R0b3rt72
|
||||
//EMAIL_FROM=rob@rdbsolutions.co.uk
|
||||
|
||||
EMAIL_SERVER_HOST=smtp.sendgrid.net
|
||||
EMAIL_SERVER_PORT=465
|
||||
EMAIL_SERVER_USER=rob@rdbsolutions.co.uk
|
||||
EMAIL_SERVER_PASSWORD=R0b3rt72
|
||||
EMAIL_FROM=no-reply@example.com
|
||||
EMAIL_SERVER_USER=apikey
|
||||
EMAIL_SERVER_PASSWORD=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
|
||||
EMAIL_FROM=rob@rdbsolutions.co.uk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
NEXT_PUBLIC_MAGIC_PK=pk_live_674E13244D1BB0A4
|
||||
MAGIC_SK=sk_live_F0E74A9557051A65
|
||||
|
||||
|
||||
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
NEXTAUTH_SECRET= # Linux: `openssl rand -hex 32` or go to https://generate-secret.now.sh/32
|
||||
|
||||
APPLE_ID=
|
||||
APPLE_TEAM_ID=
|
||||
APPLE_PRIVATE_KEY=
|
||||
APPLE_KEY_ID=
|
||||
|
||||
AUTH0_ID=
|
||||
AUTH0_SECRET=
|
||||
AUTH0_ISSUER=
|
||||
|
||||
FACEBOOK_ID=
|
||||
FACEBOOK_SECRET=
|
||||
|
||||
GITHUB_ID=
|
||||
GITHUB_SECRET=
|
||||
|
||||
GOOGLE_ID=
|
||||
GOOGLE_SECRET=
|
||||
|
||||
TWITTER_ID=
|
||||
TWITTER_SECRET=
|
||||
|
||||
//EMAIL_SERVER=smtp://username:password@smtp.example.com:587
|
||||
//EMAIL_FROM=NextAuth <noreply@example.com>
|
||||
|
||||
//DATABASE_URL=sqlite://localhost/:memory:?synchronize=true
|
||||
DATABASE_URL=mysql://nextAuthAdmin:!Sky9fish1972@77.68.17.157:3306/nextauthDB?synchronise=true
|
||||
|
||||
SECRET=SuperSecret
|
||||
+186
-147
@@ -9,6 +9,8 @@ import { getLogin } from "../../actions";
|
||||
import { setLoggedInUserId } from "../../store/accountDetails/action";
|
||||
import { parseCookies, setCookie, destroyCookie } from "nookies";
|
||||
|
||||
import { useSession, signIn, signOut } from "next-auth/react";
|
||||
|
||||
const required = (errorMsg) => (value) =>
|
||||
value || typeof value === "number" ? undefined : errorMsg;
|
||||
|
||||
@@ -118,6 +120,8 @@ let Login = (props) => {
|
||||
GG_REDIRECT_URI,
|
||||
} = props;
|
||||
|
||||
const { session, status } = useSession();
|
||||
|
||||
const router = useRouter();
|
||||
const { locale } = router;
|
||||
|
||||
@@ -201,163 +205,198 @@ let Login = (props) => {
|
||||
router.replace(ggUrl);
|
||||
};
|
||||
|
||||
if (session === "loading") {
|
||||
return <h1>loading...</h1>;
|
||||
}
|
||||
|
||||
if (session) {
|
||||
<>
|
||||
<div>Signed in as {session.user?.email}</div>
|
||||
<br />
|
||||
<button type="button" onClick={() => signOut()}>
|
||||
Signout
|
||||
</button>
|
||||
</>;
|
||||
}
|
||||
return (
|
||||
<div className="card" id="login-card">
|
||||
<div className="card-body active">
|
||||
<h3 className="heading-small card-heading">
|
||||
{t("home:login-card-title")}
|
||||
</h3>{" "}
|
||||
<form onSubmit={handleSubmit(onHandleSubmit)}>
|
||||
{validLoginID == false && (
|
||||
<div className="govuk-error-message govuk-form-group--error">
|
||||
Incorrect username or password
|
||||
</div>
|
||||
)}
|
||||
<Field
|
||||
// validate={[
|
||||
// required(
|
||||
// t("home:casesearch-search-validation-required")
|
||||
// ),
|
||||
// minLength(
|
||||
// t("home:casesearch-search-validation-minlength")
|
||||
// ),
|
||||
// ]}
|
||||
className="govuk-input"
|
||||
component={RenderTextfield}
|
||||
id="loginUserID"
|
||||
name="loginUserID"
|
||||
type="text"
|
||||
aria-describedby="basicSearch-hint"
|
||||
hint1={t("home:login-card-id-label")}
|
||||
label={t("home:login-card-id-hint")}
|
||||
/>
|
||||
|
||||
<Field
|
||||
// validate={[
|
||||
// required(
|
||||
// t("home:casesearch-search-validation-required")
|
||||
// ),
|
||||
// minLength(
|
||||
// t("home:casesearch-search-validation-minlength")
|
||||
// ),
|
||||
// ]}
|
||||
className="govuk-input"
|
||||
component={RenderPasswordfield}
|
||||
id="loginUserPassword"
|
||||
name="loginUserPassword"
|
||||
type="password"
|
||||
label="Password"
|
||||
/>
|
||||
|
||||
<div className="govuk-form-group">
|
||||
<button type="submit" className="govuk-button">
|
||||
{t("home:login-card-button")}
|
||||
<>
|
||||
<div className="card" id="login-card">
|
||||
<div className="card-body active">
|
||||
<h3 className="heading-small card-heading">
|
||||
{t("home:login-card-title")}
|
||||
</h3>{" "}
|
||||
<div className="govuk-form-group govuk-!-margin-top-4">
|
||||
{" "}
|
||||
Not Signed in
|
||||
<br />
|
||||
<button
|
||||
className="govuk-button"
|
||||
type="button"
|
||||
onClick={() => signIn()}
|
||||
>
|
||||
Magic Link Sign in
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="govuk-form-group govuk-!-margin-top-4">
|
||||
<p className="govuk-body-xs">
|
||||
<a
|
||||
onClick={() => {
|
||||
govGatwayLoginRedirect();
|
||||
}}
|
||||
className="govuk-button withChevron govuk-button-cta"
|
||||
>
|
||||
{t("home:login-via-gov-gateway-label")}
|
||||
</a>
|
||||
</p>
|
||||
<p className="govuk-body-s">
|
||||
{t("home:login-card-register-label")}{" "}
|
||||
<a
|
||||
href={
|
||||
router.locale == "cy"
|
||||
? "https://www.ete.access.service.gov.uk/login/create-account?lang=cy"
|
||||
: "https://www.ete.access.service.gov.uk/login/create-account"
|
||||
}
|
||||
className="govuk-link--no-underline"
|
||||
>
|
||||
{t("home:login-card-register-link")}
|
||||
</a>
|
||||
</p>
|
||||
<p className="govuk-body-s">
|
||||
<a
|
||||
onClick={() => {
|
||||
govGatwayForgottenLink();
|
||||
}}
|
||||
className="govuk-link--no-underline"
|
||||
>
|
||||
{t("home:login-card-forgotten-label")}{" "}
|
||||
</a>
|
||||
</p>
|
||||
<hr className="govuk-!-margin-bottom-4" />
|
||||
<p className="govuk-body-s">
|
||||
{t("home:login-comingsoon-temp-2")}
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
className="govuk-link--no-underline"
|
||||
target="_blank"
|
||||
href={t("home:login-comingsoon-forms-link-1")}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t("home:login-comingsoon-forms-label-1")}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{" "}
|
||||
<a
|
||||
className="govuk-link--no-underline"
|
||||
target="_blank"
|
||||
href={t("home:login-comingsoon-forms-link-2")}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t("home:login-comingsoon-forms-label-2")}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{" "}
|
||||
<a
|
||||
className="govuk-link--no-underline"
|
||||
target="_blank"
|
||||
href={t("home:login-comingsoon-forms-link-3")}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t("home:login-comingsoon-forms-label-3")}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{" "}
|
||||
<a
|
||||
className="govuk-link--no-underline"
|
||||
target="_blank"
|
||||
href={t("home:login-comingsoon-forms-link-4")}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t("home:login-comingsoon-forms-label-4")}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
<form onSubmit={handleSubmit(onHandleSubmit)}>
|
||||
{validLoginID == false && (
|
||||
<div className="govuk-error-message govuk-form-group--error">
|
||||
Incorrect username or password
|
||||
</div>
|
||||
)}
|
||||
<Field
|
||||
// validate={[
|
||||
// required(
|
||||
// t("home:casesearch-search-validation-required")
|
||||
// ),
|
||||
// minLength(
|
||||
// t("home:casesearch-search-validation-minlength")
|
||||
// ),
|
||||
// ]}
|
||||
className="govuk-input"
|
||||
component={RenderTextfield}
|
||||
id="loginUserID"
|
||||
name="loginUserID"
|
||||
type="text"
|
||||
aria-describedby="basicSearch-hint"
|
||||
hint1={t("home:login-card-id-label")}
|
||||
label={t("home:login-card-id-hint")}
|
||||
/>
|
||||
|
||||
<p className="govuk-body-s">
|
||||
{t("home:login-comingsoon-email-link-label")}{" "}
|
||||
<a
|
||||
href={"mailto:" + t("home:login-contact-email")}
|
||||
className="govuk-link--no-underline"
|
||||
>
|
||||
{t("home:login-contact-email")}
|
||||
</a>
|
||||
</p>
|
||||
<Field
|
||||
// validate={[
|
||||
// required(
|
||||
// t("home:casesearch-search-validation-required")
|
||||
// ),
|
||||
// minLength(
|
||||
// t("home:casesearch-search-validation-minlength")
|
||||
// ),
|
||||
// ]}
|
||||
className="govuk-input"
|
||||
component={RenderPasswordfield}
|
||||
id="loginUserPassword"
|
||||
name="loginUserPassword"
|
||||
type="password"
|
||||
label="Password"
|
||||
/>
|
||||
|
||||
<div className="govuk-form-group">
|
||||
<button type="submit" className="govuk-button">
|
||||
{t("home:login-card-button")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="govuk-form-group govuk-!-margin-top-4">
|
||||
<p className="govuk-body-xs">
|
||||
<a
|
||||
onClick={() => {
|
||||
govGatwayLoginRedirect();
|
||||
}}
|
||||
className="govuk-button withChevron govuk-button-cta"
|
||||
>
|
||||
{t("home:login-via-gov-gateway-label")}
|
||||
</a>
|
||||
</p>
|
||||
<p className="govuk-body-s">
|
||||
{t("home:login-card-register-label")}{" "}
|
||||
<a
|
||||
href={
|
||||
router.locale == "cy"
|
||||
? "https://www.ete.access.service.gov.uk/login/create-account?lang=cy"
|
||||
: "https://www.ete.access.service.gov.uk/login/create-account"
|
||||
}
|
||||
className="govuk-link--no-underline"
|
||||
>
|
||||
{t("home:login-card-register-link")}
|
||||
</a>
|
||||
</p>
|
||||
<p className="govuk-body-s">
|
||||
<a
|
||||
onClick={() => {
|
||||
govGatwayForgottenLink();
|
||||
}}
|
||||
className="govuk-link--no-underline"
|
||||
>
|
||||
{t("home:login-card-forgotten-label")}{" "}
|
||||
</a>
|
||||
</p>
|
||||
<hr className="govuk-!-margin-bottom-4" />
|
||||
<p className="govuk-body-s">
|
||||
{t("home:login-comingsoon-temp-2")}
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
className="govuk-link--no-underline"
|
||||
target="_blank"
|
||||
href={t(
|
||||
"home:login-comingsoon-forms-link-1"
|
||||
)}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t("home:login-comingsoon-forms-label-1")}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{" "}
|
||||
<a
|
||||
className="govuk-link--no-underline"
|
||||
target="_blank"
|
||||
href={t(
|
||||
"home:login-comingsoon-forms-link-2"
|
||||
)}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t("home:login-comingsoon-forms-label-2")}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{" "}
|
||||
<a
|
||||
className="govuk-link--no-underline"
|
||||
target="_blank"
|
||||
href={t(
|
||||
"home:login-comingsoon-forms-link-3"
|
||||
)}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t("home:login-comingsoon-forms-label-3")}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{" "}
|
||||
<a
|
||||
className="govuk-link--no-underline"
|
||||
target="_blank"
|
||||
href={t(
|
||||
"home:login-comingsoon-forms-link-4"
|
||||
)}
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t("home:login-comingsoon-forms-label-4")}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
|
||||
<p className="govuk-body-s">
|
||||
{t("home:login-comingsoon-email-link-label")}{" "}
|
||||
<a
|
||||
href={"mailto:" + t("home:login-contact-email")}
|
||||
className="govuk-link--no-underline"
|
||||
>
|
||||
{t("home:login-contact-email")}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <LoadingOverlay
|
||||
{/* <LoadingOverlay
|
||||
active={showSpinnerState}
|
||||
spinner
|
||||
text="Logging in"
|
||||
/> */}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
ValueTransformer,
|
||||
} from "typeorm"
|
||||
|
||||
const transformer = {
|
||||
date: {
|
||||
from: (date ) => date && new Date(parseInt(date, 10)),
|
||||
to: (date) => date?.valueOf().toString(),
|
||||
},
|
||||
bigint: {
|
||||
from: (bigInt) => bigInt && parseInt(bigInt, 10),
|
||||
to: (bigInt) => bigInt?.toString(),
|
||||
},
|
||||
}
|
||||
|
||||
@Entity({ name: "users" })
|
||||
export class UserEntity {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id = undefined;
|
||||
|
||||
@Column("varchar")
|
||||
name!
|
||||
|
||||
|
||||
@Column({ type: "varchar", nullable: true, unique: true })
|
||||
email!: string | null
|
||||
|
||||
@Column({ type: "varchar", nullable: true, transformer: transformer.date })
|
||||
emailVerified!: string | null
|
||||
|
||||
@Column("varchar")
|
||||
image!: string | null
|
||||
|
||||
@OneToMany(() => SessionEntity, (session) => session.userId)
|
||||
sessions!: SessionEntity[]
|
||||
|
||||
@OneToMany(() => AccountEntity, (account) => account.userId)
|
||||
accounts!: AccountEntity[]
|
||||
}
|
||||
|
||||
@Entity({ name: "accounts" })
|
||||
export class AccountEntity {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id!: string
|
||||
|
||||
@Column({ type: "uuid" })
|
||||
userId!: string
|
||||
|
||||
@Column()
|
||||
type!: string
|
||||
|
||||
@Column()
|
||||
provider!: string
|
||||
|
||||
@Column()
|
||||
providerAccountId!: string
|
||||
|
||||
@Column("varchar")
|
||||
refresh_token!: string | null
|
||||
|
||||
@Column("varchar")
|
||||
access_token!: string | null
|
||||
|
||||
@Column({
|
||||
nullable: true,
|
||||
type: "bigint",
|
||||
transformer: transformer.bigint,
|
||||
})
|
||||
expires_at!: number | null
|
||||
|
||||
@Column("varchar")
|
||||
token_type!: string | null
|
||||
|
||||
@Column("varchar")
|
||||
scope!: string | null
|
||||
|
||||
@Column("varchar")
|
||||
id_token!: string | null
|
||||
|
||||
@Column("varchar")
|
||||
session_state!: string | null
|
||||
|
||||
@ManyToOne(() => UserEntity, (user) => user.accounts, {
|
||||
createForeignKeyConstraints: true,
|
||||
})
|
||||
user!: UserEntity
|
||||
}
|
||||
|
||||
@Entity({ name: "sessions" })
|
||||
export class SessionEntity {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id!: string
|
||||
|
||||
@Column({ unique: true })
|
||||
sessionToken!: string
|
||||
|
||||
@Column({ type: "uuid" })
|
||||
userId!: string
|
||||
|
||||
@Column({ transformer: transformer.date })
|
||||
expires!: string
|
||||
|
||||
@ManyToOne(() => UserEntity, (user) => user.sessions)
|
||||
user!: UserEntity
|
||||
}
|
||||
|
||||
@Entity({ name: "verification_tokens" })
|
||||
export class VerificationTokenEntity {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id!: string
|
||||
|
||||
@Column()
|
||||
token!: string
|
||||
|
||||
@Column()
|
||||
identifier!: string
|
||||
|
||||
@Column({ transformer: transformer.date })
|
||||
expires!: string
|
||||
}
|
||||
+7
-1
@@ -11,6 +11,9 @@
|
||||
"dependencies": {
|
||||
"@googlemaps/react-wrapper": "^1.1.24",
|
||||
"@magic-sdk/admin": "^1.4.0",
|
||||
"@next-auth/prisma-adapter": "^1.0.3",
|
||||
"@next-auth/typeorm-legacy-adapter": "^1.0.3",
|
||||
"@prisma/client": "3.3.0",
|
||||
"@webdeb/next-styles": "^1.1.1",
|
||||
"applicationinsights": "^2.2.0",
|
||||
"axios": "^0.24.0",
|
||||
@@ -35,6 +38,7 @@
|
||||
"magic-sdk": "^8.1.0",
|
||||
"moment": "^2.29.1",
|
||||
"multer": "^1.4.4",
|
||||
"mysql": "^2.18.1",
|
||||
"nanoid": "^3.2.0",
|
||||
"next": "^12.0.8",
|
||||
"next-auth": "^4.3.1",
|
||||
@@ -65,6 +69,7 @@
|
||||
"sass": "1.32",
|
||||
"sass-loader": "^12.4.0",
|
||||
"swr": "^1.1.2",
|
||||
"typeorm": "^0.3.5",
|
||||
"webpack": "^5.66.0",
|
||||
"xml2js": "^0.4.23",
|
||||
"xmldom": "^0.6.0",
|
||||
@@ -74,6 +79,7 @@
|
||||
"devDependencies": {
|
||||
"eslint": "^8.6.0",
|
||||
"eslint-config-next": "^12.0.8",
|
||||
"prettier": "2.5.1"
|
||||
"prettier": "2.5.1",
|
||||
"prisma": "3.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,37 @@
|
||||
import NextAuth from "next-auth";
|
||||
import Providers from "next-auth/providers";
|
||||
import { Magic } from "@magic-sdk/admin";
|
||||
|
||||
const magic = new Magic(process.env.MAGIC_SK);
|
||||
import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter";
|
||||
import EmailProvider from "next-auth/providers/email";
|
||||
import * as entities from "../../../lib/entities";
|
||||
|
||||
// For more information on each option (and a full list of options) go to
|
||||
// https://next-auth.js.org/configuration/options
|
||||
export default NextAuth({
|
||||
session: {
|
||||
jwt: true,
|
||||
},
|
||||
pages: {
|
||||
// override signIn page so we can integrate with Magic
|
||||
signIn: "/auth/signin",
|
||||
},
|
||||
// https://next-auth.js.org/configuration/providers
|
||||
providers: [
|
||||
Providers.Credentials({
|
||||
name: "Magic Link",
|
||||
credentials: {
|
||||
didToken: { label: "DID Token", type: "text" },
|
||||
},
|
||||
async authorize({ didToken }, req) {
|
||||
// validate magic DID token
|
||||
magic.token.validate(didToken);
|
||||
|
||||
// fetch user metadata
|
||||
const metadata = await magic.users.getMetadataByToken(didToken);
|
||||
|
||||
// return user info
|
||||
return { ...metadata };
|
||||
EmailProvider({
|
||||
server: {
|
||||
host: process.env.EMAIL_SERVER_HOST,
|
||||
port: process.env.EMAIL_SERVER_PORT,
|
||||
auth: {
|
||||
user: process.env.EMAIL_SERVER_USER,
|
||||
pass: process.env.EMAIL_SERVER_PASSWORD,
|
||||
},
|
||||
},
|
||||
from: process.env.EMAIL_FROM,
|
||||
maxAge: 10 * 60, // Magic links are valid for 10 min only
|
||||
}),
|
||||
],
|
||||
adapter: TypeORMLegacyAdapter({
|
||||
type: "mysql",
|
||||
database:
|
||||
"mysql://nextAuthAdmin:!Sky9fish1972@77.68.17.157:3306/nextauthDB?synchronise=true",
|
||||
synchronize: false,
|
||||
}),
|
||||
secret: "SuperSecret",
|
||||
session: {
|
||||
jwt: true,
|
||||
pages: {},
|
||||
theme: "dark",
|
||||
debug: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { signIn } from "next-auth/client";
|
||||
|
||||
// magic-sdk is only availabile in the browser
|
||||
const magic =
|
||||
typeof window !== "undefined" &&
|
||||
new Magic(process.env.NEXT_PUBLIC_MAGIC_PK || "a");
|
||||
|
||||
export default function SignIn() {
|
||||
const router = useRouter();
|
||||
const { register, handleSubmit } = useForm();
|
||||
|
||||
const onSubmit = async ({ email }) => {
|
||||
if (!magic) throw new Error(`magic not defined`);
|
||||
|
||||
// login with Magic
|
||||
const didToken = await magic.auth.loginWithMagicLink({ email });
|
||||
|
||||
// sign in with NextAuth
|
||||
await signIn("credentials", {
|
||||
didToken,
|
||||
callbackUrl: router.query["callbackUrl"],
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<input
|
||||
{...register("email", { required: true })}
|
||||
placeholder="nick@example.com"
|
||||
/>
|
||||
|
||||
<button type="submit">Sign in</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
datasource db {
|
||||
provider = "mysql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
model Account {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
type String
|
||||
provider String
|
||||
providerAccountId String
|
||||
refresh_token String? @db.Text
|
||||
access_token String? @db.Text
|
||||
expires_at Int?
|
||||
token_type String?
|
||||
scope String?
|
||||
id_token String? @db.Text
|
||||
session_state String?
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([provider, providerAccountId])
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @default(cuid())
|
||||
sessionToken String @unique
|
||||
userId String
|
||||
expires DateTime
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
name String?
|
||||
email String? @unique
|
||||
emailVerified DateTime?
|
||||
image String?
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
}
|
||||
|
||||
model VerificationToken {
|
||||
identifier String
|
||||
token String @unique
|
||||
expires DateTime
|
||||
|
||||
@@unique([identifier, token])
|
||||
}
|
||||
+3818
-2371
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user