Fix: authentication and ingest API

This commit is contained in:
Harshith Mullapudi 2025-06-16 21:50:30 +05:30
parent 33eae2619a
commit 941ce8e4fa
11 changed files with 158 additions and 128 deletions

View File

@ -11,6 +11,7 @@
C.O.R.E lets you create a private, portable, open-source memory space for LLMs, all stored locally for full data control. You decide what to share or connect with other tools, managing exactly what gets recalled and where. C.O.R.E lets you create a private, portable, open-source memory space for LLMs, all stored locally for full data control. You decide what to share or connect with other tools, managing exactly what gets recalled and where.
C.O.R.E is built for two reasons: C.O.R.E is built for two reasons:
1. To give you complete ownership of your memory, stored locally and accessible across any app needing LLM context. 1. To give you complete ownership of your memory, stored locally and accessible across any app needing LLM context.
2. To help SOL (your AI assistant) access your context, facts, and preferences for more relevant and personalized responses. 2. To help SOL (your AI assistant) access your context, facts, and preferences for more relevant and personalized responses.
@ -25,11 +26,11 @@ C.O.R.E supports **temporal reasoning**, **relational memory**, and **traceabili
## Getting Started ## Getting Started
### Prerequisites ### Prerequisites
1. Docker 1. Docker
2. OpenAI API Key 2. OpenAI API Key
### Run C.O.R.E locally by
### Run C.O.R.E locally by
1. **Copy Environment Variables** 1. **Copy Environment Variables**
@ -97,7 +98,6 @@ You can also interact with C.O.R.E. programmatically via its APIs.
{ {
"episodeBody": "I love playing badminton", "episodeBody": "I love playing badminton",
"referenceTime": "2024-06-01T12:00:00Z", "referenceTime": "2024-06-01T12:00:00Z",
"type": "Conversation", // or "Text"
"source": "user", // Which tool or user is ingesting "source": "user", // Which tool or user is ingesting
"spaceId": "your-space-id", // optional, for multiple spaces "spaceId": "your-space-id", // optional, for multiple spaces
"sessionId": "your-session-id" // optional "sessionId": "your-session-id" // optional

View File

@ -76,9 +76,10 @@ export function getUserQueue(userId: string) {
} }
export const IngestBodyRequest = z.object({ export const IngestBodyRequest = z.object({
name: z.string(),
episodeBody: z.string(), episodeBody: z.string(),
referenceTime: z.string(), referenceTime: z.string(),
type: z.enum([EpisodeType.Conversation, EpisodeType.Text]), // Assuming these are the EpisodeType values metadata: z.record(z.union([z.string(), z.number()])),
source: z.string(), source: z.string(),
spaceId: z.string().optional(), spaceId: z.string().optional(),
sessionId: z.string().optional(), sessionId: z.string().optional(),

View File

@ -1,10 +1,17 @@
import { import {
createCookie,
redirect, redirect,
type ActionFunctionArgs, type ActionFunctionArgs,
type LoaderFunctionArgs, type LoaderFunctionArgs,
type MetaFunction, type MetaFunction,
} from "@remix-run/node"; } from "@remix-run/node";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { Form, useNavigation } from "@remix-run/react"; import { Form, useNavigation } from "@remix-run/react";
import { Inbox, Loader, Mail } from "lucide-react"; import { Inbox, Loader, Mail } from "lucide-react";
import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { typedjson, useTypedLoaderData } from "remix-typedjson";
@ -16,8 +23,7 @@ import { FormButtons } from "~/components/ui/FormButtons";
import { Header1 } from "~/components/ui/Headers"; import { Header1 } from "~/components/ui/Headers";
import { Input } from "~/components/ui/input"; import { Input } from "~/components/ui/input";
import { Paragraph } from "~/components/ui/Paragraph"; import { Paragraph } from "~/components/ui/Paragraph";
import { TextLink } from "~/components/ui/TextLink"; import { Cookie } from "@mjackson/headers";
import { authenticator } from "~/services/auth.server"; import { authenticator } from "~/services/auth.server";
import { getUserId } from "~/services/session.server"; import { getUserId } from "~/services/session.server";
import { import {
@ -65,10 +71,14 @@ export async function loader({ request }: LoaderFunctionArgs): Promise<any> {
} }
} }
const magicLinkSent = new Cookie(request.headers.get("cookie") ?? "").has(
"core:magiclink",
);
return typedjson( return typedjson(
{ {
emailLoginEnabled: true, emailLoginEnabled: true,
magicLinkSent: session.has("core:magiclink"), magicLinkSent,
magicLinkError, magicLinkError,
}, },
{ {
@ -93,14 +103,19 @@ export async function action({ request }: ActionFunctionArgs) {
.parse(payload); .parse(payload);
if (action === "send") { if (action === "send") {
return await authenticator.authenticate("email-link", request); const headers = await authenticator
.authenticate("email-link", request)
.catch((headers) => headers);
throw redirect("/login/magic", { headers });
} else { } else {
const session = await getUserSession(request); const myCookie = createCookie("core:magiclink");
session.unset("core:magiclink");
return redirect("/magic", { return redirect("/login/magic", {
headers: { headers: {
"Set-Cookie": await commitSession(session), "Set-Cookie": await myCookie.serialize("", {
maxAge: 0,
path: "/",
}),
}, },
}); });
} }
@ -130,80 +145,81 @@ export default function LoginMagicLinkPage() {
<Form method="post"> <Form method="post">
<div className="flex flex-col items-center justify-center"> <div className="flex flex-col items-center justify-center">
{data.magicLinkSent ? ( {data.magicLinkSent ? (
<> <Card className="min-w-[400px] rounded-md p-3">
<Header1 className="pb-6 text-center text-xl leading-7 font-normal md:text-xl lg:text-2xl"> <CardHeader className="flex flex-col items-start">
We've sent you a magic link! <CardTitle className="mb-0 text-lg">
</Header1> {" "}
<Fieldset className="flex w-full flex-col items-center gap-y-2"> We've sent you a magic link!
<Inbox className="text-primary mb-4 h-12 w-12" /> </CardTitle>
<Paragraph className="mb-6 text-center"> <CardDescription>
We sent you an email which contains a magic link that will log We sent you an email which contains a magic link that will log
you in to your account. you in to your account.
</Paragraph> </CardDescription>
</CardHeader>
<Fieldset className="flex w-full flex-col items-center gap-y-2 px-2">
<FormButtons <FormButtons
cancelButton={ cancelButton={
<Button <Button
type="submit" type="submit"
name="action" name="action"
value="reset" value="reset"
variant="link" variant="secondary"
data-action="re-enter email" data-action="re-enter email"
> >
Re-enter email Re-enter email
</Button> </Button>
} }
confirmButton={ confirmButton={<></>}
<Button
variant="ghost"
data-action="log in using another option"
>
Log in using another option
</Button>
}
/> />
</Fieldset> </Fieldset>
</> </Card>
) : ( ) : (
<> <Card className="min-w-[400px] rounded-md p-3">
<Header1 className="pb-4 font-semibold sm:text-2xl md:text-3xl lg:text-4xl"> <CardHeader className="flex flex-col items-start">
Welcome <CardTitle className="mb-0 text-lg">Welcome</CardTitle>
</Header1> <CardDescription>
<Paragraph variant="base" className="mb-6 text-center"> Create an account or login using email
Create an account or login using email </CardDescription>
</Paragraph> </CardHeader>
<Fieldset className="flex w-full flex-col items-center gap-y-2"> <CardContent className="pt-2">
<Input <Fieldset className="flex w-full flex-col items-center gap-y-2">
type="email" <Input
name="email" type="email"
spellCheck={false} name="email"
placeholder="Email Address" className="h-9"
required spellCheck={false}
autoFocus placeholder="Email Address"
/> required
autoFocus
/>
<Button <Button
name="action" name="action"
value="send" value="send"
type="submit" type="submit"
variant="secondary" variant="secondary"
size="lg" size="lg"
disabled={isLoading} disabled={isLoading}
data-action="send a magic link" data-action="send a magic link"
> >
{isLoading ? ( {isLoading ? (
<Loader className="mr-2 size-5" color="white" /> <Loader className="mr-2 size-5" color="white" />
) : ( ) : (
<Mail className="text-text-bright mr-2 size-5" /> <Mail className="text-text-bright mr-2 size-5" />
)} )}
{isLoading ? ( {isLoading ? (
<span className="text-text-bright">Sending</span> <span className="text-text-bright">Sending</span>
) : ( ) : (
<span className="text-text-bright">Send a magic link</span> <span className="text-text-bright">
)} Send a magic link
</Button> </span>
{data.magicLinkError && <>{data.magicLinkError}</>} )}
</Fieldset> </Button>
</> {data.magicLinkError && <>{data.magicLinkError}</>}
</Fieldset>
</CardContent>
</Card>
)} )}
</div> </div>
</Form> </Form>

View File

@ -5,9 +5,11 @@ import { json } from "@remix-run/node";
export const SearchBodyRequest = z.object({ export const SearchBodyRequest = z.object({
query: z.string(), query: z.string(),
spaceId: z.string().optional(),
startTime: z.string().optional(), startTime: z.string().optional(),
endTime: z.string().optional(), endTime: z.string().optional(),
// These are not supported yet, but need to support these
spaceId: z.string().optional(),
limit: z.number().optional(), limit: z.number().optional(),
maxBfsDepth: z.number().optional(), maxBfsDepth: z.number().optional(),
includeInvalidated: z.boolean().optional(), includeInvalidated: z.boolean().optional(),

View File

@ -13,7 +13,9 @@ const authenticator = new Authenticator<AuthUser>();
const isGoogleAuthSupported = const isGoogleAuthSupported =
typeof env.AUTH_GOOGLE_CLIENT_ID === "string" && typeof env.AUTH_GOOGLE_CLIENT_ID === "string" &&
typeof env.AUTH_GOOGLE_CLIENT_SECRET === "string"; env.AUTH_GOOGLE_CLIENT_ID.length > 0 &&
typeof env.AUTH_GOOGLE_CLIENT_SECRET === "string" &&
env.AUTH_GOOGLE_CLIENT_SECRET.length > 0;
if (env.AUTH_GOOGLE_CLIENT_ID && env.AUTH_GOOGLE_CLIENT_SECRET) { if (env.AUTH_GOOGLE_CLIENT_ID && env.AUTH_GOOGLE_CLIENT_SECRET) {
addGoogleStrategy( addGoogleStrategy(

View File

@ -58,11 +58,6 @@ function buildTransportOptions(): MailTransportOptions {
} }
export async function sendMagicLinkEmail(options: any): Promise<void> { export async function sendMagicLinkEmail(options: any): Promise<void> {
// Auto redirect when in development mode
if (env.NODE_ENV === "development") {
throw redirect(options.magicLink);
}
logger.debug("Sending magic link email", { logger.debug("Sending magic link email", {
emailAddress: options.emailAddress, emailAddress: options.emailAddress,
}); });
@ -77,6 +72,7 @@ export async function sendMagicLinkEmail(options: any): Promise<void> {
logger.error("Error sending magic link email", { logger.error("Error sending magic link email", {
error: JSON.stringify(error), error: JSON.stringify(error),
}); });
throw error; throw error;
} }
} }

View File

@ -16,6 +16,9 @@ const emailStrategy = new EmailLinkStrategy(
sendEmail: sendMagicLinkEmail, sendEmail: sendMagicLinkEmail,
secret, secret,
magicEndpoint: `${APP_ORIGIN}/magic`, magicEndpoint: `${APP_ORIGIN}/magic`,
cookie: {
name: "core:magiclink",
},
}, },
async ({ email }: { email: string }) => { async ({ email }: { email: string }) => {
logger.info("Magic link user authenticated", { email }); logger.info("Magic link user authenticated", { email });

View File

@ -18,6 +18,7 @@
"@core/database": "workspace:*", "@core/database": "workspace:*",
"@core/types": "workspace:*", "@core/types": "workspace:*",
"@opentelemetry/api": "1.9.0", "@opentelemetry/api": "1.9.0",
"@mjackson/headers": "0.11.1",
"@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-avatar": "^1.0.4",

View File

@ -1,58 +1,58 @@
version: "3.8" version: "3.8"
services: services:
core: # core:
container_name: core-app # container_name: core-app
image: redplanethq/core:${VERSION} # image: redplanethq/core:${VERSION}
environment: # environment:
- NODE_ENV=${NODE_ENV} # - NODE_ENV=${NODE_ENV}
- DATABASE_URL=${DATABASE_URL} # - DATABASE_URL=${DATABASE_URL}
- DIRECT_URL=${DIRECT_URL} # - DIRECT_URL=${DIRECT_URL}
- SESSION_SECRET=${SESSION_SECRET} # - SESSION_SECRET=${SESSION_SECRET}
- ENCRYPTION_KEY=${ENCRYPTION_KEY} # - ENCRYPTION_KEY=${ENCRYPTION_KEY}
- MAGIC_LINK_SECRET=${MAGIC_LINK_SECRET} # - MAGIC_LINK_SECRET=${MAGIC_LINK_SECRET}
- LOGIN_ORIGIN=${LOGIN_ORIGIN} # - LOGIN_ORIGIN=${LOGIN_ORIGIN}
- APP_ORIGIN=${APP_ORIGIN} # - APP_ORIGIN=${APP_ORIGIN}
- REDIS_HOST=${REDIS_HOST} # - REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT} # - REDIS_PORT=${REDIS_PORT}
- REDIS_TLS_DISABLED=${REDIS_TLS_DISABLED} # - REDIS_TLS_DISABLED=${REDIS_TLS_DISABLED}
- NEO4J_URI=${NEO4J_URI} # - NEO4J_URI=${NEO4J_URI}
- NEO4J_USERNAME=${NEO4J_USERNAME} # - NEO4J_USERNAME=${NEO4J_USERNAME}
- NEO4J_PASSWORD=${NEO4J_PASSWORD} # - NEO4J_PASSWORD=${NEO4J_PASSWORD}
- OPENAI_API_KEY=${OPENAI_API_KEY} # - OPENAI_API_KEY=${OPENAI_API_KEY}
- AUTH_GOOGLE_CLIENT_ID=${AUTH_GOOGLE_CLIENT_ID} # - AUTH_GOOGLE_CLIENT_ID=${AUTH_GOOGLE_CLIENT_ID}
- AUTH_GOOGLE_CLIENT_SECRET=${AUTH_GOOGLE_CLIENT_SECRET} # - AUTH_GOOGLE_CLIENT_SECRET=${AUTH_GOOGLE_CLIENT_SECRET}
- ENABLE_EMAIL_LOGIN=${ENABLE_EMAIL_LOGIN} # - ENABLE_EMAIL_LOGIN=${ENABLE_EMAIL_LOGIN}
ports: # ports:
- "3000:3000" # - "3033:3000"
depends_on: # depends_on:
- postgres # - postgres
- redis # - redis
- neo4j # - neo4j
networks: # networks:
- core # - core
postgres: # postgres:
container_name: core-postgres # container_name: core-postgres
image: postgres:15 # image: postgres:15
environment: # environment:
- POSTGRES_USER=${POSTGRES_USER} # - POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD} # - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB} # - POSTGRES_DB=${POSTGRES_DB}
ports: # ports:
- "5432:5432" # - "5432:5432"
volumes: # volumes:
- postgres_data:/var/lib/postgresql/data # - postgres_data:/var/lib/postgresql/data
networks: # networks:
- core # - core
redis: # redis:
container_name: core-redis # container_name: core-redis
image: redis:7 # image: redis:7
ports: # ports:
- "6379:6379" # - "6379:6379"
networks: # networks:
- core # - core
neo4j: neo4j:
container_name: core-neo4j container_name: core-neo4j

8
pnpm-lock.yaml generated
View File

@ -48,6 +48,9 @@ importers:
'@core/types': '@core/types':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/types version: link:../../packages/types
'@mjackson/headers':
specifier: 0.11.1
version: 0.11.1
'@nichtsam/remix-auth-email-link': '@nichtsam/remix-auth-email-link':
specifier: 3.0.0 specifier: 3.0.0
version: 3.0.0(remix-auth@4.2.0) version: 3.0.0(remix-auth@4.2.0)
@ -1520,6 +1523,9 @@ packages:
'@mjackson/headers@0.10.0': '@mjackson/headers@0.10.0':
resolution: {integrity: sha512-U1Eu1gF979k7ZoIBsJyD+T5l9MjtPONsZfoXfktsQHPJD0s7SokBGx+tLKDLsOY+gzVYAWS0yRFDNY8cgbQzWQ==} resolution: {integrity: sha512-U1Eu1gF979k7ZoIBsJyD+T5l9MjtPONsZfoXfktsQHPJD0s7SokBGx+tLKDLsOY+gzVYAWS0yRFDNY8cgbQzWQ==}
'@mjackson/headers@0.11.1':
resolution: {integrity: sha512-uXXhd4rtDdDwkqAuGef1nuafkCa1NlTmEc1Jzc0NL4YiA1yON1NFXuqJ3hOuKvNKQwkiDwdD+JJlKVyz4dunFA==}
'@mjackson/headers@0.9.0': '@mjackson/headers@0.9.0':
resolution: {integrity: sha512-1WFCu2iRaqbez9hcYYI611vcH1V25R+fDfOge/CyKc8sdbzniGfy/FRhNd3DgvFF4ZEEX2ayBrvFHLtOpfvadw==} resolution: {integrity: sha512-1WFCu2iRaqbez9hcYYI611vcH1V25R+fDfOge/CyKc8sdbzniGfy/FRhNd3DgvFF4ZEEX2ayBrvFHLtOpfvadw==}
@ -9776,6 +9782,8 @@ snapshots:
'@mjackson/headers@0.10.0': {} '@mjackson/headers@0.10.0': {}
'@mjackson/headers@0.11.1': {}
'@mjackson/headers@0.9.0': {} '@mjackson/headers@0.9.0': {}
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':

View File

@ -57,6 +57,7 @@
"NEO4J_USERNAME", "NEO4J_USERNAME",
"NEO4J_PASSWORD", "NEO4J_PASSWORD",
"OPENAI_API_KEY", "OPENAI_API_KEY",
"MAGIC_LINK_SECRET" "MAGIC_LINK_SECRET",
"ENABLE_EMAIL_LOGIN"
] ]
} }