This commit is contained in:
2022-04-11 11:07:29 +01:00
parent 412b7c15b6
commit 67102497f0
8 changed files with 4266 additions and 2584 deletions
+48 -4
View File
@@ -77,11 +77,55 @@ API_ROOT = "http://localhost:3000"
GOOGLE_MAP_API_KEY = AIzaSyCzeVrHt544bp_72YCFkw21eDsmI2JEsQo 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_PORT=465
EMAIL_SERVER_USER=rob@rdbsolutions.co.uk EMAIL_SERVER_USER=apikey
EMAIL_SERVER_PASSWORD=R0b3rt72 EMAIL_SERVER_PASSWORD=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
EMAIL_FROM=no-reply@example.com EMAIL_FROM=rob@rdbsolutions.co.uk
NEXT_PUBLIC_MAGIC_PK=pk_live_674E13244D1BB0A4 NEXT_PUBLIC_MAGIC_PK=pk_live_674E13244D1BB0A4
MAGIC_SK=sk_live_F0E74A9557051A65 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
View File
@@ -9,6 +9,8 @@ import { getLogin } from "../../actions";
import { setLoggedInUserId } from "../../store/accountDetails/action"; import { setLoggedInUserId } from "../../store/accountDetails/action";
import { parseCookies, setCookie, destroyCookie } from "nookies"; import { parseCookies, setCookie, destroyCookie } from "nookies";
import { useSession, signIn, signOut } from "next-auth/react";
const required = (errorMsg) => (value) => const required = (errorMsg) => (value) =>
value || typeof value === "number" ? undefined : errorMsg; value || typeof value === "number" ? undefined : errorMsg;
@@ -118,6 +120,8 @@ let Login = (props) => {
GG_REDIRECT_URI, GG_REDIRECT_URI,
} = props; } = props;
const { session, status } = useSession();
const router = useRouter(); const router = useRouter();
const { locale } = router; const { locale } = router;
@@ -201,163 +205,198 @@ let Login = (props) => {
router.replace(ggUrl); 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 ( return (
<div className="card" id="login-card"> <>
<div className="card-body active"> <div className="card" id="login-card">
<h3 className="heading-small card-heading"> <div className="card-body active">
{t("home:login-card-title")} <h3 className="heading-small card-heading">
</h3>{" "} {t("home:login-card-title")}
<form onSubmit={handleSubmit(onHandleSubmit)}> </h3>{" "}
{validLoginID == false && ( <div className="govuk-form-group govuk-!-margin-top-4">
<div className="govuk-error-message govuk-form-group--error"> {" "}
Incorrect username or password Not Signed in
</div> <br />
)} <button
<Field className="govuk-button"
// validate={[ type="button"
// required( onClick={() => signIn()}
// t("home:casesearch-search-validation-required") >
// ), Magic Link Sign in
// 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")}
</button> </button>
</div> </div>
</form> <form onSubmit={handleSubmit(onHandleSubmit)}>
<div className="govuk-form-group govuk-!-margin-top-4"> {validLoginID == false && (
<p className="govuk-body-xs"> <div className="govuk-error-message govuk-form-group--error">
<a Incorrect username or password
onClick={() => { </div>
govGatwayLoginRedirect(); )}
}} <Field
className="govuk-button withChevron govuk-button-cta" // validate={[
> // required(
{t("home:login-via-gov-gateway-label")} // t("home:casesearch-search-validation-required")
</a> // ),
</p> // minLength(
<p className="govuk-body-s"> // t("home:casesearch-search-validation-minlength")
{t("home:login-card-register-label")}{" "} // ),
<a // ]}
href={ className="govuk-input"
router.locale == "cy" component={RenderTextfield}
? "https://www.ete.access.service.gov.uk/login/create-account?lang=cy" id="loginUserID"
: "https://www.ete.access.service.gov.uk/login/create-account" name="loginUserID"
} type="text"
className="govuk-link--no-underline" aria-describedby="basicSearch-hint"
> hint1={t("home:login-card-id-label")}
{t("home:login-card-register-link")} label={t("home:login-card-id-hint")}
</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"> <Field
{t("home:login-comingsoon-email-link-label")}{" "} // validate={[
<a // required(
href={"mailto:" + t("home:login-contact-email")} // t("home:casesearch-search-validation-required")
className="govuk-link--no-underline" // ),
> // minLength(
{t("home:login-contact-email")} // t("home:casesearch-search-validation-minlength")
</a> // ),
</p> // ]}
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>
</div>
{/* <LoadingOverlay {/* <LoadingOverlay
active={showSpinnerState} active={showSpinnerState}
spinner spinner
text="Logging in" text="Logging in"
/> */} /> */}
</div> </div>
</>
); );
}; };
+125
View File
@@ -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
View File
@@ -11,6 +11,9 @@
"dependencies": { "dependencies": {
"@googlemaps/react-wrapper": "^1.1.24", "@googlemaps/react-wrapper": "^1.1.24",
"@magic-sdk/admin": "^1.4.0", "@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", "@webdeb/next-styles": "^1.1.1",
"applicationinsights": "^2.2.0", "applicationinsights": "^2.2.0",
"axios": "^0.24.0", "axios": "^0.24.0",
@@ -35,6 +38,7 @@
"magic-sdk": "^8.1.0", "magic-sdk": "^8.1.0",
"moment": "^2.29.1", "moment": "^2.29.1",
"multer": "^1.4.4", "multer": "^1.4.4",
"mysql": "^2.18.1",
"nanoid": "^3.2.0", "nanoid": "^3.2.0",
"next": "^12.0.8", "next": "^12.0.8",
"next-auth": "^4.3.1", "next-auth": "^4.3.1",
@@ -65,6 +69,7 @@
"sass": "1.32", "sass": "1.32",
"sass-loader": "^12.4.0", "sass-loader": "^12.4.0",
"swr": "^1.1.2", "swr": "^1.1.2",
"typeorm": "^0.3.5",
"webpack": "^5.66.0", "webpack": "^5.66.0",
"xml2js": "^0.4.23", "xml2js": "^0.4.23",
"xmldom": "^0.6.0", "xmldom": "^0.6.0",
@@ -74,6 +79,7 @@
"devDependencies": { "devDependencies": {
"eslint": "^8.6.0", "eslint": "^8.6.0",
"eslint-config-next": "^12.0.8", "eslint-config-next": "^12.0.8",
"prettier": "2.5.1" "prettier": "2.5.1",
"prisma": "3.3.0"
} }
} }
+29 -25
View File
@@ -1,33 +1,37 @@
import NextAuth from "next-auth"; import NextAuth from "next-auth";
import Providers from "next-auth/providers"; import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter";
import { Magic } from "@magic-sdk/admin"; import EmailProvider from "next-auth/providers/email";
import * as entities from "../../../lib/entities";
const magic = new Magic(process.env.MAGIC_SK);
// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
export default NextAuth({ export default NextAuth({
session: { // https://next-auth.js.org/configuration/providers
jwt: true,
},
pages: {
// override signIn page so we can integrate with Magic
signIn: "/auth/signin",
},
providers: [ providers: [
Providers.Credentials({ EmailProvider({
name: "Magic Link", server: {
credentials: { host: process.env.EMAIL_SERVER_HOST,
didToken: { label: "DID Token", type: "text" }, port: process.env.EMAIL_SERVER_PORT,
}, auth: {
async authorize({ didToken }, req) { user: process.env.EMAIL_SERVER_USER,
// validate magic DID token pass: process.env.EMAIL_SERVER_PASSWORD,
magic.token.validate(didToken); },
// fetch user metadata
const metadata = await magic.users.getMetadataByToken(didToken);
// return user info
return { ...metadata };
}, },
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,
},
}); });
-36
View File
@@ -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>
);
}
+53
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff