Skip to content

Commit

Permalink
feat: hydra integration
Browse files Browse the repository at this point in the history
chore: renaming folder

chore: moving some of the change to the rest layer instead of graphql

chore: some iteration on hydra

feat: adding scope middleware

chore: testing client credentials

chore: adding appId to know whether a request comes from hydra or kratos

chore: update docs

fix: test?
  • Loading branch information
Nicolas Burtey committed Oct 10, 2023
1 parent 689efef commit 892d7c0
Show file tree
Hide file tree
Showing 106 changed files with 14,630 additions and 24 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export MATTERMOST_WEBHOOK_URL="https://chat.galoy.io/hooks/sometoken"

export KRATOS_PG_CON="postgres://dbuser:secret@localhost:5433/default?sslmode=disable"


export UNSECURE_DEFAULT_LOGIN_CODE="000000"
export UNSECURE_IP_FROM_REQUEST_OBJECT=true

Expand Down
3 changes: 3 additions & 0 deletions apps/consent/.env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export CLIENT_ID=569c1e70-7875-47af-a6b5-1e1bf3a7b905
export CLIENT_SECRET=oWSmag.dm~EGsl7iCj8groGVjZ
export AUTHORIZATION_URL=http://127.0.0.1:4444/oauth2/auth?client_id=569c1e70-7875-47af-a6b5-1e1bf3a7b905&scope=offline%20transactions:read&response_type=code&redirect_uri=http://localhost:3000/&state=kfISr3GhH0rqheByU6A6hqIG_f14pCGkZLSCUTHnvlI
3 changes: 3 additions & 0 deletions apps/consent/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
35 changes: 35 additions & 0 deletions apps/consent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
24 changes: 24 additions & 0 deletions apps/consent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# login-consent-provider

This is the User Login and Consent flow for the Galoy stack over the OAuth2 service (Hydra) in NextJs.

## Local set up:
Install dependencies using
```
yarn install
```

Run next.js app in dev mode
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```

###Environment variables
```
AUTH_URL
HYDRA_ADMIN_URL
```
32 changes: 32 additions & 0 deletions apps/consent/app/components/button/primary-button-component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use client";
import React, { ButtonHTMLAttributes } from "react";
import { experimental_useFormStatus as useFormStatus } from "react-dom";
import Loader from "../loader";

interface PrimaryButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
label?: string;
children: React.ReactNode;
disabled?: boolean;
}

const PrimaryButton: React.FC<PrimaryButtonProps> = ({
children,
disabled = false,
...buttonProps
}) => {
const { pending } = useFormStatus();
const loadOrDisable = pending || disabled;
return (
<button
disabled={loadOrDisable}
{...buttonProps}
className={`flex-1 ${
!loadOrDisable ? "bg-orange-500" : "bg-orange-600"
} text-white p-2 rounded-lg text-sm hover:bg-orange-600`}
>
{loadOrDisable ? <Loader size="15px" /> : children}
</button>
);
};

export default PrimaryButton;
28 changes: 28 additions & 0 deletions apps/consent/app/components/button/secondary-button-component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";
import React, { ButtonHTMLAttributes } from "react";
import { experimental_useFormStatus as useFormStatus } from "react-dom";

interface SecondaryButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
label?: string;
children: React.ReactNode;
disabled?: boolean;
}

const SecondaryButton: React.FC<SecondaryButtonProps> = ({
children,
disabled = false,
...buttonProps
}) => {
const { pending } = useFormStatus();
return (
<button
disabled={pending || disabled}
{...buttonProps}
className={`flex-1 border-orange-500 bg-transparent text-gray-800 p-2 rounded-lg text-sm border-2 transition-all duration-300 ease-in-out hover:bg-orange-500 hover:text-white hover:border-transparent`}
>
{children}
</button>
);
};

export default SecondaryButton;
57 changes: 57 additions & 0 deletions apps/consent/app/components/captcha-challenge/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"use client";
import { sendPhoneCode } from "@/app/login/phone/server-actions";
import { memo, useCallback, useEffect } from "react";
import { toast } from "react-toastify";

const CaptchaChallengeComponent: React.FC<{
id: string;
challenge: string;
formData: {
login_challenge: string;
phone: string;
remember: string;
};
}> = ({ id, challenge, formData }) => {
console.log({ id, challenge, formData });

const captchaHandler = useCallback(
(captchaObj: any) => {
console.log("---------", captchaObj);
const onSuccess = async () => {
const result = captchaObj.getValidate();
const res = await sendPhoneCode(result, formData);
if (res?.error) {
toast.error(res.message);
}
};
captchaObj.appendTo("#captcha");
captchaObj
.onReady(() => {
captchaObj.verify();
})
.onSuccess(onSuccess)
.onError((err: unknown) => {
console.debug("[Captcha error]:", err);
});
},
[formData]
);

useEffect(() => {
// @ts-ignore
window.initGeetest(
{
gt: id,
challenge: challenge,
offline: false,
new_captcha: true,
product: "bind",
lang: "en",
},
captchaHandler
);
}, [captchaHandler, id, challenge]);

return <div data-testid="captcha_container" id="captcha"></div>;
};
export const CaptchaChallenge = memo(CaptchaChallengeComponent);
19 changes: 19 additions & 0 deletions apps/consent/app/components/card/card.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.card {
padding: 1.5rem;
margin: 0.5rem;
width: 91%;
}

@media (min-width: 768px) {
.card {
background-color: white;
border-radius: 0.5rem;
box-shadow:
rgba(0, 0, 0, 0.144) 0px 1px 3px 0px,
rgba(27, 31, 35, 0.144) 0px 0px 0px 1px;
width: auto;
margin: auto;
width: 25em;
padding: 1.5em;
}
}
12 changes: 12 additions & 0 deletions apps/consent/app/components/card/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React, { ReactNode } from "react"
import styles from "./card.module.css"

interface CardProps {
children: ReactNode
}

const Card: React.FC<CardProps> = ({ children }) => {
return <div className={styles.card}>{children}</div>
}

export default Card
18 changes: 18 additions & 0 deletions apps/consent/app/components/form-component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React, { FormEvent, ReactNode } from "react";

type FromProps = {
action?: (formData: FormData) => void;
onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void;
children?: React.ReactNode;
};

function FormComponent({ action, children, onSubmit } : FromProps) {
return (
<form action={action} className="flex flex-col" onSubmit={onSubmit}>
{children}
</form>
);
};

export default FormComponent;

11 changes: 11 additions & 0 deletions apps/consent/app/components/heading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React, { Children } from "react";

interface headingProps {
children: React.ReactNode;
}

function Heading({ children }: headingProps) {
return <h1 className="text-center mb-4 text-xl font-semibold">{children}</h1>;
}

export default Heading;
21 changes: 21 additions & 0 deletions apps/consent/app/components/input-component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { InputHTMLAttributes } from "react"

interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string
id: string
}

const InputComponent: React.FC<InputProps> = ({ label, id, ...inputProps }) => {
return (
<div className="mb-4">
{label ? (
<label htmlFor={id} className="block mb-2 text-sm font-medium text-gray-900">
{label}
</label>
) : null}
<input {...inputProps} id={id} className="p-2 border rounded w-full" />
</div>
)
}

export default InputComponent
18 changes: 18 additions & 0 deletions apps/consent/app/components/loader/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react";
import styles from "./loader.module.css";

interface loaderProps {
size?: string;
}

export default function loader({ size = "38px" }: loaderProps) {
return (
<span
style={{
width: size,
height: size,
}}
className={styles.loader}
></span>
);
}
17 changes: 17 additions & 0 deletions apps/consent/app/components/loader/loader.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.loader {
border: 3px solid #d3d3d3;
border-bottom-color: transparent;
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}

@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
12 changes: 12 additions & 0 deletions apps/consent/app/components/logo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";
import Image from "next/image";

const Logo: React.FC = () => {
return (
<div className="flex justify-center mb-4">
<Image src="/blink_logo.svg" alt="Galoy" width={120} height={120} />
</div>
);
};

export default Logo;
15 changes: 15 additions & 0 deletions apps/consent/app/components/main-container/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React, { ReactNode } from "react"

interface MainContentProps {
children: ReactNode
}

const MainContent: React.FC<MainContentProps> = ({ children }) => {
return (
<main className="min-h-screen flex items-start md:items-center justify-center p-1 md:p-4">
{children}
</main>
)
}

export default MainContent
21 changes: 21 additions & 0 deletions apps/consent/app/components/scope-item/scope-item.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.item_container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
background-color: var(--grey5);
border-radius: 0.5rem;
margin-bottom: 0.5em;
position: relative;
}

.custom_label {
position: relative;
cursor: pointer;
flex-grow: 1;
}

.text-gray-700 {
color: #4a4a4a;
}
21 changes: 21 additions & 0 deletions apps/consent/app/components/scope-item/scope-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from "react"
import styles from "./scope-item.module.css"

interface ScopeItemProps {
scope: string
}

export const ScopeItem: React.FC<ScopeItemProps> = ({ scope }) => (
<label className={styles.item_container} htmlFor={scope}>
<span className={styles.custom_label}>{scope}</span>
<input
type="checkbox"
id={scope}
value={scope}
name="grant_scope"
className={styles.grant_scope}
/>
</label>
)

export default ScopeItem
Loading

0 comments on commit 892d7c0

Please sign in to comment.