Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 2 additions & 63 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,8 @@
import type {NextRequest} from 'next/server'
import {NextResponse} from 'next/server'
import {isValidRedirect} from "@core/util/redirect";
import {middleware as editionMiddleware} from "@edition/Middleware";

export function middleware(request: NextRequest) {

const {searchParams, pathname} = request.nextUrl
const callbackUrl = searchParams.get('callbackUrl')
const selectNamespace = searchParams.get('selectNamespace')
const authPaths = ["/email", "/login", "/password", "/redirect", "/register"]
const isAuthPath = authPaths.some(path => pathname.startsWith(path))

if (callbackUrl && isValidRedirect(callbackUrl)) {
let response: NextResponse

if (isAuthPath) {
response = NextResponse.next()
} else {
const redirectUrl = new URL('/redirect', request.url)
redirectUrl.searchParams.set('callbackUrl', callbackUrl)
if (selectNamespace) redirectUrl.searchParams.set('selectNamespace', selectNamespace)
response = NextResponse.redirect(redirectUrl)
}

response.cookies.set('codezero_callback', callbackUrl, {
path: '/',
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 5,
})

if (selectNamespace) {
response.cookies.set('codezero_selectNamespace', selectNamespace, {
path: '/',
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 5,
})
}

return response
}

const callbackCookie = request.cookies.get('codezero_callback')?.value
const selectNamespaceCookie = request.cookies.get('codezero_selectNamespace')?.value

if (callbackCookie) {
if (!isAuthPath) {
const redirectUrl = new URL('/redirect', request.url)
redirectUrl.searchParams.set('callbackUrl', callbackCookie)
if (selectNamespaceCookie) redirectUrl.searchParams.set('selectNamespace', selectNamespaceCookie)
return NextResponse.redirect(redirectUrl)
}

if (pathname.startsWith("/redirect")) {
const url = request.nextUrl.clone()
if (!searchParams.has('callbackUrl')) {
url.searchParams.set('callbackUrl', callbackCookie)
if (selectNamespaceCookie) url.searchParams.set('selectNamespace', selectNamespaceCookie)
return NextResponse.redirect(url)
}
}
}

return NextResponse.next()

return editionMiddleware(request)
}

export const config = {
Expand Down
6 changes: 6 additions & 0 deletions src/packages/ce/src/Middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type {NextRequest} from 'next/server'
import {NextResponse} from 'next/server'

export function middleware(_request: NextRequest) {
return NextResponse.next()
}
165 changes: 3 additions & 162 deletions src/packages/ce/src/user/pages/UserRedirectPage.tsx
Original file line number Diff line number Diff line change
@@ -1,167 +1,8 @@
"use client"

import React from "react";
import {
Alert,
Button,
Card,
Flex,
InputDescription,
InputLabel,
SelectContent,
SelectInput,
SelectItem,
SelectItemText,
SelectPortal,
SelectTrigger,
SelectValue,
SelectViewport,
Spacing,
Text,
TextInput,
useService,
useStore
} from "@code0-tech/pictor";
import {useUserSession} from "@edition/user/hooks/User.session.hook";
import {useRouter, useSearchParams} from "next/navigation";
import {IconApiApp, IconCheck, IconChevronDown} from "@tabler/icons-react";
import {isValidRedirect} from "@core/util/redirect";
import CardSection from "@code0-tech/pictor/dist/components/card/CardSection";
import {OrganizationService} from "@edition/organization/services/Organization.service";
import {OrganizationView} from "@edition/organization/services/Organization.view";
import {notFound} from "next/navigation";

export const UserRedirectPage: React.FC = () => {

const userSession = useUserSession()
const params = useSearchParams()
const callbackUrl = params.get("callbackUrl")
const selectNamespace = params.get("selectNamespace")
const router = useRouter()
const isValidCallback = isValidRedirect(callbackUrl)
const organizationService = useService(OrganizationService)
const organizationStore = useStore(OrganizationService)
const [namespaceId, setNamespaceId] = React.useState<string | null>(null);

const namespaces: ({
title?: string
id?: string
})[] = React.useMemo(
() => [{
title: `Personal organization of user @${userSession?.user?.username}`,
id: userSession?.user?.namespace?.id!
}, ...organizationService.values().filter((organization: OrganizationView) => organization.userAbilities).map((organization: OrganizationView) => ({
id: organization.namespace?.id!,
title: `Organization with name @${organization.name}`
}))],
[organizationStore.length, userSession]
)

if (userSession === null) router.push("/login")

return <>
<Text size={"lg"} hierarchy={"primary"} display={"block"}>
Authorize application
</Text>
<Spacing spacing={"xs"}/>
<Text size={"md"} hierarchy={"tertiary"} display={"block"}>
Build high-class workflows, endpoints and software without coding
</Text>
<Spacing spacing={"xl"}/>
{
!isValidCallback && (
<>
<Alert color={"error"}>
This is not a valid and verified application. Please go back if you don't trust the source of this
link.
</Alert>
<Spacing spacing={"xl"}/>
</>
)
}
<InputLabel>Application</InputLabel>
<InputDescription>The external application you are granting access to.</InputDescription>
<TextInput left={<IconApiApp size={16}/>} disabled value={`${callbackUrl}`}/>
<Spacing spacing={"xl"}/>
{
selectNamespace && (
<>
<InputLabel>Organization</InputLabel>
<InputDescription>Choose the organization this application can access.</InputDescription>
<SelectInput onValueChange={setNamespaceId}>
<SelectTrigger asChild>
<Flex justify={"space-between"} align={"center"} px={0.7} py={0.7}>
<Text hierarchy={"secondary"}>
<SelectValue placeholder={"Select an organization"}/>
</Text>
<IconChevronDown size={13}/>
</Flex>
</SelectTrigger>
<SelectPortal>
<SelectContent>
<SelectViewport key={namespaces.length}>
{
namespaces.map(namespace => (
<SelectItem value={namespace.id ?? ""}>
<SelectItemText>
<Text>{namespace.title}</Text>
</SelectItemText>
</SelectItem>
))
}
</SelectViewport>
</SelectContent>
</SelectPortal>
</SelectInput>
<Spacing spacing={"xl"}/>
</>
)
}
<InputLabel>Permissions</InputLabel>
<InputDescription>The application is requesting permission to perform the following actions.</InputDescription>
<Card color={"secondary"}>
<CardSection border>
<Flex style={{gap: "0.7rem"}} align={"center"}>
<IconCheck style={{minWidth: "1rem"}} size={16}/>
<Text>Can access your account information like email, username, and other stored information on your
account</Text>
</Flex>
</CardSection>
<CardSection border>
<Flex style={{gap: "0.7rem"}} align={"center"}>
<IconCheck size={16}/>
<Text>Can access, manage and edit organizations</Text>
</Flex>
</CardSection>
<CardSection border>
<Flex style={{gap: "0.7rem"}} align={"center"}>
<IconCheck size={16}/>
<Text>Can access, manage and edit licenses</Text>
</Flex>
</CardSection>
</Card>
<Spacing spacing={"xl"}/>
<Flex style={{gap: "0.7rem"}} align={"center"}>
<Button type={"submit"} onClick={() => {
document.cookie = "codezero_callback=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT";
document.cookie = "codezero_selectNamespace=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT";
router.push("/")
}} data-qa-selector={"auth-login-send"} color={isValidCallback ? "secondary" : "success"} w={"100%"}
mb={1.3}>
No, go back
</Button>
<Button type={"submit"} onClick={() => {
if (callbackUrl && userSession) {
document.cookie = "codezero_callback=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT";
document.cookie = "codezero_selectNamespace=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT";
const targetURL = new URL(callbackUrl)
targetURL.searchParams.set('token', userSession?.token ?? "")
if (selectNamespace && namespaceId) targetURL.searchParams.set('namespace', namespaceId)
router.push(targetURL.toString())
}
}} data-qa-selector={"auth-login-send"} color={isValidCallback ? "success" : "error"} w={"100%"} mb={1.3}>
{isValidCallback ? "Authorize" : "Authorize and ignore warning"}
</Button>
</Flex>
</>

}
return notFound()
}
12 changes: 12 additions & 0 deletions src/packages/ce/src/user/services/User.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
Mutation,
Query,
User,
UsersCreateCraterTokenInput,
UsersCreateCraterTokenPayload,
UsersCreateInput,
UsersCreatePayload,
UsersEmailVerificationInput,
Expand Down Expand Up @@ -36,6 +38,7 @@ import {
} from "@code0-tech/sagittarius-graphql-types";
import {GraphqlClient} from "@core/util/graphql-client";
import createMutation from "./mutations/User.create.mutation.graphql";
import createCraterTokenMutation from "./mutations/User.createCraterToken.mutation.graphql";
import updateMutation from "./mutations/User.update.mutation.graphql";
import loginMutation from "./mutations/User.login.mutation.graphql";
import logoutMutation from "./mutations/User.logout.mutation.graphql";
Expand Down Expand Up @@ -185,6 +188,15 @@ export class UserService extends ReactiveArrayService<User> {
return result.data?.usersCreate ?? undefined
}

async usersCreateCraterToken(): Promise<UsersCreateCraterTokenPayload | undefined> {
const result = await this.client.mutate<Mutation, UsersCreateCraterTokenInput>({
mutation: createCraterTokenMutation,
variables: {}
})

return result.data?.usersCreateCraterToken ?? undefined
}

async usersUpdate(payload: UsersUpdateInput): Promise<UsersUpdatePayload | undefined> {
const result = await this.client.mutate<Mutation, UsersUpdateInput>({
mutation: updateMutation,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
mutation createCraterToken {
usersCreateCraterToken(input: {}) {
errors {
...on Error {
errorCode,
details {
__typename
}
}
}
token {
token
}
}
}
64 changes: 64 additions & 0 deletions src/packages/cloud/src/Middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type {NextRequest} from 'next/server'
import {NextResponse} from 'next/server'
import {isValidRedirect} from "@core/util/redirect";

export function middleware(request: NextRequest) {

const {searchParams, pathname} = request.nextUrl
const callbackUrl = searchParams.get('callbackUrl')
const selectNamespace = searchParams.get('selectNamespace')
const authPaths = ["/email", "/login", "/password", "/redirect", "/register", "/callback"]
const isAuthPath = authPaths.some(path => pathname.startsWith(path))
const isRedirectPath = pathname.startsWith("/redirect")

const cookieOptions = {
path: '/',
sameSite: 'lax' as const,
secure: request.nextUrl.protocol === 'https:',
maxAge: 60 * 5,
}

if (callbackUrl && isValidRedirect(callbackUrl)) {

if (isRedirectPath) return NextResponse.next()
if (!isAuthPath) {
// Any other page: send the user on to the consent screen.
const redirectUrl = new URL('/redirect', request.url)
redirectUrl.searchParams.set('callbackUrl', callbackUrl)
if (selectNamespace) redirectUrl.searchParams.set('selectNamespace', selectNamespace)
const response = NextResponse.redirect(redirectUrl)
response.cookies.set('codezero_callback', callbackUrl, cookieOptions)
if (selectNamespace) response.cookies.set('codezero_selectNamespace', selectNamespace, cookieOptions)
return response
}

const redirectUrl = request.nextUrl.clone()
redirectUrl.searchParams.delete('callbackUrl')
redirectUrl.searchParams.delete('selectNamespace')
const response = NextResponse.redirect(redirectUrl)
response.cookies.set('codezero_callback', callbackUrl, cookieOptions)
if (selectNamespace) response.cookies.set('codezero_selectNamespace', selectNamespace, cookieOptions)
return response
}

const callbackCookie = request.cookies.get('codezero_callback')?.value
const selectNamespaceCookie = request.cookies.get('codezero_selectNamespace')?.value

if (callbackCookie) {
if (!isAuthPath) {
const redirectUrl = new URL('/redirect', request.url)
redirectUrl.searchParams.set('callbackUrl', callbackCookie)
if (selectNamespaceCookie) redirectUrl.searchParams.set('selectNamespace', selectNamespaceCookie)
return NextResponse.redirect(redirectUrl)
}

if (isRedirectPath && !searchParams.has('callbackUrl')) {
const url = request.nextUrl.clone()
url.searchParams.set('callbackUrl', callbackCookie)
if (selectNamespaceCookie) url.searchParams.set('selectNamespace', selectNamespaceCookie)
return NextResponse.redirect(url)
}
}

return NextResponse.next()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#import "@ce-internal/application/services/fragments/Application.fragment.graphql"

query Application(
$firstIdentityProvider: Int,
$afterIdentityProvider: String
) {
application {
...Application
}
}
Loading