Skip to content

Commit

Permalink
feat: creating api throught dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicolas Burtey committed Oct 23, 2023
1 parent ca3c8b0 commit 668dc02
Show file tree
Hide file tree
Showing 23 changed files with 403 additions and 53 deletions.
55 changes: 43 additions & 12 deletions apps/consent/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import { redirect } from "next/navigation"
import React from "react"
import { cookies, headers } from "next/headers"
import Link from "next/link"
import { headers, cookies } from "next/headers"
import { redirect } from "next/navigation"

import { hydraClient } from "../../services/hydra"
import InputComponent from "../components/input-component"
import PrimaryButton from "../components/button/primary-button-component"
import SecondaryButton from "../components/button/secondary-button-component"
import Card from "../components/card"
import MainContent from "../components/main-container"
import Logo from "../components/logo"
import Heading from "../components/heading"
import SubHeading from "../components/sub-heading"
import FormComponent from "../components/form-component"
import Heading from "../components/heading"
import InputComponent from "../components/input-component"
import Logo from "../components/logo"
import MainContent from "../components/main-container"
import Separator from "../components/separator"
import PrimaryButton from "../components/button/primary-button-component"
import SecondaryButton from "../components/button/secondary-button-component"
import SubHeading from "../components/sub-heading"
import { LoginType, SubmitValue } from "../index.types"

import { LoginEmailResponse } from "./email-login.types"

import { env } from "@/env"
import authApi from "@/services/galoy-auth"
import axios from "axios"

// this page is for login via email
interface LoginProps {
Expand Down Expand Up @@ -94,8 +95,8 @@ async function submitForm(formData: FormData): Promise<LoginEmailResponse | void
value: email,
remember,
}),
{ secure: true },
)
{ secure: true, sameSite: "lax" }
);

const params = new URLSearchParams({
login_challenge,
Expand Down Expand Up @@ -138,6 +139,36 @@ const Login = async ({ searchParams }: { searchParams: LoginProps }) => {
redirect(String(response.redirect_to))
}

const useSecureCookies = env.DASHBOARD_URL.startsWith("https://");
const cookiePrefix = useSecureCookies ? "__Secure-" : "";
const cookieNameSession = `${cookiePrefix}next-auth.session-token`;
const cookie = cookies().get(cookieNameSession);

console.log("cookie", cookies().toString());

if (cookie) {
const response = await axios(`${env.DASHBOARD_URL}/api/auth/session`, {
method: "GET",
headers: {
Cookie: cookies().toString(),
},
});
const userId = response?.data?.userData?.data?.me?.id;
console.log(userId);

if (userId) {
const response2 = await hydraClient.acceptOAuth2LoginRequest({
loginChallenge: login_challenge,
acceptOAuth2LoginRequest: {
subject: userId,
remember: true,
acr: "2", // FIXME
},
});
redirect(response2.data.redirect_to);
}
}

return (
<MainContent>
<Card>
Expand Down
4 changes: 2 additions & 2 deletions apps/consent/app/login/phone/server-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ export const sendPhoneCode = async (
value: phone,
remember: remember,
}),
{ secure: true },
)
{ secure: true, sameSite: "lax" }
);

const params = new URLSearchParams({
login_challenge,
Expand Down
2 changes: 1 addition & 1 deletion apps/consent/app/logout/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const submitForm = async (form: FormData) => {
await hydraClient.rejectOAuth2LogoutRequest({
logoutChallenge: logout_challenge,
})
redirect("https://www.blink.sh/")
redirect("/")
}
const response = await hydraClient.acceptOAuth2LogoutRequest({
logoutChallenge: logout_challenge,
Expand Down
2 changes: 1 addition & 1 deletion apps/consent/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ dotenv.config({ path: ".env.test" })

export default defineConfig({
e2e: {
baseUrl: "http://127.0.0.1:3000",
baseUrl: "http://localhost:3000",
// setupNodeEvents(on, config) {},
},
defaultCommandTimeout: 60000,
Expand Down
4 changes: 2 additions & 2 deletions apps/consent/dev/get-url.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
NEXTAUTH_URL="http://localhost:3000/"

code_client=$(hydra create client \
--endpoint http://127.0.0.1:4445 \
--endpoint http://localhost:4445 \
--grant-type authorization_code,refresh_token \
--response-type code,id_token \
--format json \
Expand All @@ -12,7 +12,7 @@ code_client=$(hydra create client \
export CLIENT_ID=$(echo $code_client | jq -r '.client_id')
export CLIENT_SECRET=$(echo $code_client | jq -r '.client_secret')

AUTHORIZATION_URL="http://127.0.0.1:4444/oauth2/auth?client_id=$CLIENT_ID&scope=offline%20transactions:read&response_type=code&redirect_uri=$NEXTAUTH_URL&state=kfISr3GhH0rqheByU6A6hqIG_f14pCGkZLSCUTHnvlI"
AUTHORIZATION_URL="http://localhost:4444/oauth2/auth?client_id=$CLIENT_ID&scope=offline%20transactions:read&response_type=code&redirect_uri=$NEXTAUTH_URL&state=kfISr3GhH0rqheByU6A6hqIG_f14pCGkZLSCUTHnvlI"
echo "export CLIENT_ID=$CLIENT_ID" > ./.env.test
echo "export CLIENT_SECRET=$CLIENT_SECRET" >> ./.env.test
echo "export AUTHORIZATION_URL=$AUTHORIZATION_URL" >> ./.env.test
2 changes: 2 additions & 0 deletions apps/consent/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const env = createEnv({
server: {
HYDRA_ADMIN_URL: z.string().default("http://localhost:4445"),
CORE_AUTH_URL: z.string().default("http://localhost:4002/auth"),
DASHBOARD_URL: z.string().default("http://localhost:3001"),
},
shared: {
GRAPHQL_ENDPOINT: z.string().default("http://localhost:4002/graphql"),
Expand All @@ -13,5 +14,6 @@ export const env = createEnv({
CORE_AUTH_URL: process.env.CORE_AUTH_URL,
HYDRA_ADMIN_URL: process.env.HYDRA_ADMIN_URL,
GRAPHQL_ENDPOINT: process.env.GRAPHQL_ENDPOINT,
DASHBOARD_URL: process.env.DASHBOARD_URL,
},
})
2 changes: 1 addition & 1 deletion apps/dashboard/.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# REPLACE THIS IT IS FOR TESTING
NEXTAUTH_URL=https://c890-2405-201-301c-5b67-89d6-bd56-6afb-6294.ngrok-free.app
NEXTAUTH_URL=http://localhost:3001
NEXTAUTH_SECRET="thisismysecret"
# 2db7666c39074da4b399e8b5116ef2c6
# 2cc1869e52ad47df848a6519b63bb4f4
19 changes: 19 additions & 0 deletions apps/dashboard/app/api-keys/callback/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NextResponse } from "next/server"
import { getOauth2Client } from "../client"
import { env } from "@/env"

export async function GET(request: Request) {
const client = await getOauth2Client()

const params = client.callbackParams(request.url)

// const tokenSet = await client.oauthCallback(
const tokenSet = await client.callback(
`${env.NEXTAUTH_URL}/api-keys/callback`,
params,
{ state: params.state },
)
console.log("received and validated tokens %j", tokenSet)

return NextResponse.json({ ...tokenSet })
}
16 changes: 16 additions & 0 deletions apps/dashboard/app/api-keys/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { env } from "@/env"
import { Issuer } from "openid-client"

const clientId = env.CLIENT_ID_APP_API_KEY
const clientSecret = env.CLIENT_SECRET_APP_API_KEY

export const getOauth2Client = async () => {
const GaloyIssuer = await Issuer.discover(env.HYDRA_PUBLIC)

return new GaloyIssuer.Client({
client_id: clientId,
client_secret: clientSecret,
redirect_uris: [`${env.NEXTAUTH_URL}/api-keys/callback`],
response_types: ["code"],
})
}
20 changes: 20 additions & 0 deletions apps/dashboard/app/api-keys/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { redirect } from "next/navigation"
import { getOauth2Client } from "./client"

const crypto = require("crypto")

function generateSecureRandomString(length: number) {
return crypto.randomBytes(length).toString("hex").slice(0, length)
}

export async function GET(request: Request) {
const client = await getOauth2Client()
const randomString = generateSecureRandomString(16)

const authorizationUri = client.authorizationUrl({
scope: "transactions:read payments:send openid",
state: randomString,
})

redirect(authorizationUri)
}
28 changes: 23 additions & 5 deletions apps/dashboard/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import NextAuth, { AuthOptions } from "next-auth"

import { ApolloQueryResult } from "@apollo/client"

import { fetchUserData } from "@/services/graphql/queries/me-data"
import NextAuth, { AuthOptions } from "next-auth"
import { env } from "@/env"
import { MeQuery } from "@/services/graphql/generated"

const useSecureCookies = process.env.NEXTAUTH_URL?.startsWith("https://")
const cookiePrefix = useSecureCookies ? "__Secure-" : ""

import { ApolloQueryResult } from "@apollo/client"

declare module "next-auth" {
interface Profile {
id: string
Expand All @@ -17,7 +19,10 @@ declare module "next-auth" {
}
}

const type = "oauth" as const
const heightHours = 8 * 60 * 60 * 1000
const expires = new Date(Date.now() + heightHours)

const type = "oauth" as const;
export const authOptions: AuthOptions = {
providers: [
{
Expand Down Expand Up @@ -68,6 +73,19 @@ export const authOptions: AuthOptions = {
return session
},
},
cookies: {
sessionToken: {
name: `${cookiePrefix}next-auth.session-token`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
// domain: ".localhost", // FIXME: use env variable
secure: useSecureCookies,
expires,
},
},
},
}

const handler = NextAuth(authOptions)
Expand Down
5 changes: 5 additions & 0 deletions apps/dashboard/app/keys/functions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use server";

export const deleteKey = async (index: string) => {
console.log(index, "session id")
}
105 changes: 105 additions & 0 deletions apps/dashboard/app/keys/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { authOptions } from "../api/auth/[...nextauth]/route";
import { env } from "@/env";
import ResultDisplay from "@/components/keys/result-display";

const addKey = async () => {
"use server"

redirect("/api-keys")
}

// FIXME: userId not necessary?
const deleteKeys = async () => {
"use server"

const session = await getServerSession(authOptions)
const userId = session?.sub

const subject = userId
const CLIENT_ID_APP_API_KEY = env.CLIENT_ID_APP_API_KEY;

const baseUrl = env.HYDRA_ADMIN;
const url = `${baseUrl}/admin/oauth2/auth/sessions/consent?subject=${subject}&client=${CLIENT_ID_APP_API_KEY}`;

try {
const response = await fetch(url, {
method: 'DELETE',
});

if (!response.ok) {
const errorData = await response.json();
throw new Error('Server-side error: ' + errorData.error);
}

const data = await response.json();
console.log('Success:', data);
} catch (error) {
if (error instanceof Error) console.error('Error:', error.message);
}

redirect("/keys")
}

type ConsentObject = {
grant_scope: string[],
grant_access_token_audience: any[],
session: {
access_token: object,
id_token: object
},
remember: boolean,
remember_for: number,
handled_at: string,
consent_request: {
challenge: string,
requested_scope: string[],
requested_access_token_audience: any[],
skip: boolean,
subject: string,
oidc_context: object,
client: {
client_id: string,
[key: string]: any // This is to cover other properties of the 'client' object.
},
[key: string]: any // This is to cover other properties of the 'consent_request' object.
},
[key: string]: any // This is to cover other properties of the main object.
};

export default async function page() {
const session = await getServerSession(authOptions)
const userId = session?.sub

let keys: ConsentObject[] = [];

try {
const url = `http://localhost:4445/admin/oauth2/auth/sessions/consent?subject=${userId}`;
const response = await fetch(url);
const result = await response.json() as ConsentObject[];
keys = result.filter(item => item.consent_request.client.client_id === env.CLIENT_ID_APP_API_KEY)

console.dir(keys, { depth: null})
} catch (error) {
console.error('Error fetching consent session:', error);
}

return (
<>
<div className="grid-flow-col grid p-6">
<form action={addKey}>
<button className="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600">Add key</button>
</form>
<form action={deleteKeys}>
<button className="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600">Delete keys</button>
</form>
</div>

{keys.length ? <div className="p-6">
<h2 className="text-2xl font-semibold mb-8">Existing keys</h2>
<ResultDisplay data={keys} />
</div>: null}
</>
)
}
1 change: 1 addition & 0 deletions apps/dashboard/app/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ interface UrlInfo {
export const URLS: Record<string, UrlInfo> = {
"/": { title: "Home", protected: true },
"/transactions": { title: "Transactions", protected: true },
"/keys": { title: "Keys", protected: true },
}
Loading

0 comments on commit 668dc02

Please sign in to comment.