Skip to content

Commit

Permalink
feat: join improvements (#149)
Browse files Browse the repository at this point in the history
* chore: clean packages

* chore: fix script

* feat: UI imporovements

* remove skip email verify

* skip email verify

* feat: use APP_URL

* fix: saml error when adding

* feat: saml fixes

* Minor tweaks

* remove console.log

---------

Co-authored-by: Vince Loewe <vince@lunary.ai>
  • Loading branch information
hughcrt and vincelwt committed Mar 25, 2024
1 parent 59c439d commit 67eaefe
Show file tree
Hide file tree
Showing 34 changed files with 523 additions and 594 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test-build-deploy.yml
Expand Up @@ -16,7 +16,7 @@ jobs:
# env:
# DATABASE_URL: ${{ secrets.DATABASE_URL }}
# JWT_SECRET: ${{ secrets.JWT_SECRET }}
# NEXT_PUBLIC_APP_URL: http://localhost:8080
# APP_URL: http://localhost:8080
# API_URL: http://localhost:3333
# LUNARY_PUBLIC_KEY: 259d2d94-9446-478a-ae04-484de705b522
# OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Expand Up @@ -200,8 +200,8 @@ entrypoint.sh


# Python
.venv
venv
__pycache__/
*.pyc

venv
test_*.py
21 changes: 19 additions & 2 deletions CONTRIBUTING.md
Expand Up @@ -6,6 +6,23 @@
4. Copy the content of `packages/backend/.env.example` to `packages/backend/.env` and fill the missing values
5. Copy the content of `packages/frontend/.env.example` to `packages/backend/.env`
6. Run `npm install`
7. Run `npm run dev`
7. Run `npm run migrate:db`
8. Run `npm run dev`

You can now open the dashboard at `http://localhost:8080`. When using our JS or Python SDK, you need to set the ennvironment variable `LUNARY_API_URL` to `http://localhost:3333`.
You can now open the dashboard at `http://localhost:8080`. When using our JS or Python SDK, you need to set the environment variable `LUNARY_API_URL` to `http://localhost:3333`.

## Contributing Guidelines

We welcome contributions to this project!

When contributing, please follow these guidelines:

- Before starting work on a new feature or bug fix, open an issue to discuss the proposed changes. This allows for coordination and avoids duplication of effort.
- Fork the repository and create a new branch for your changes. Use a descriptive branch name that reflects the purpose of your changes.
- Write clear, concise commit messages that describe the purpose of each commit.
- Make sure to update any relevant documentation, including README files and code comments.
- Make sure all tests pass before submitting a pull request.
- When submitting a pull request, provide a detailed description of your changes and reference any related issues.
- Be responsive to feedback and be willing to make changes to your pull request if requested.

Thank you for your contributions!
2 changes: 1 addition & 1 deletion ops
Submodule ops updated from c03dfd to 8c08ef
22 changes: 4 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions packages/backend/.env.example
@@ -1,10 +1,9 @@
DATABASE_URL="postgresql://postgres:password@your-host:5432/postgres"
JWT_SECRET=yoursupersecret
NEXT_PUBLIC_APP_URL=http://localhost:8080
SKIP_EMAIL_VERIFY=true
APP_URL=http://localhost:8080


# optionnal (for the playground, evaluation and radar features)
# optional (for the playground, evaluation and radar features)
LUNARY_PUBLIC_KEY=259d2d94-9446-478a-ae04-484de705b522
OPENAI_API_KEY=sk-...
OPENROUTER_API_KEY=sk-...
Expand Down
55 changes: 32 additions & 23 deletions packages/backend/src/api/v1/auth/index.ts
Expand Up @@ -46,13 +46,14 @@ auth.post("/method", async (ctx: Context) => {
auth.post("/signup", async (ctx: Context) => {
const bodySchema = z.object({
email: z.string().email().transform(sanitizeEmail),
password: z.string().min(6),
password: z.string().min(6).optional(), // optional if SAML flow
name: z.string(),
orgName: z.string().optional(),
projectName: z.string().optional(),
employeeCount: z.string().optional(),
orgId: z.string().optional(),
token: z.string().optional(),
redirectUrl: z.string().optional(),
signupMethod: z.enum(["signup", "join"]),
})

Expand All @@ -65,30 +66,41 @@ auth.post("/signup", async (ctx: Context) => {
employeeCount,
orgId,
signupMethod,
redirectUrl,
token,
} = bodySchema.parse(ctx.request.body)

// Spamming hotfix
if (orgName?.includes("https://") || name.includes("http://")) {
ctx.throw(403, "Bad request")
}

const [existingUser] = await sql`
select * from account where lower(email) = lower(${email})
`
if (signupMethod === "signup") {
const { user, org } = await sql.begin(async (sql) => {
const plan = process.env.DEFAULT_PLAN || "free"

const [existingUser] = await sql`
select * from account where lower(email) = lower(${email})
`

if (!password) {
ctx.throw(403, "Password is required")
}

if (existingUser) {
ctx.throw(403, "User already exists")
}

const [org] =
await sql`insert into org ${sql({ name: orgName || `${name}'s Org`, plan })} returning *`

const newUser = {
name,
passwordHash: await hashPassword(password),
passwordHash: await hashPassword(password!),
email,
orgId: org.id,
role: "owner",
verified: process.env.SKIP_EMAIL_VERIFY ? true : false,
verified: !process.env.RESEND_KEY ? true : false,
lastLoginAt: new Date(),
}

Expand All @@ -114,7 +126,7 @@ auth.post("/signup", async (ctx: Context) => {
projectId: project.id,
apiKey: project.id,
}
sql`
await sql`
insert into api_key ${sql(publicKey)}
`
const privateKey = [
Expand Down Expand Up @@ -151,25 +163,24 @@ auth.post("/signup", async (ctx: Context) => {
return
} else if (signupMethod === "join") {
const { payload } = await verifyJwt(token!)

if (payload.email !== email) {
ctx.throw(403, "Wrong email")
ctx.throw(403, "Invalid token")
}

const newUser = {
const update = {
name,
passwordHash: await hashPassword(password),
email,
orgId,
role: "member",
verified: true,
singleUseToken: null,
}

if (password) {
update.passwordHash = await hashPassword(password)
}
const [user] = await sql`
update account set
name = ${newUser.name},
password_hash = ${newUser.passwordHash},
verified = true,
single_use_token = null
where email = ${newUser.email}

await sql`
update account set ${sql(update)}
where email = ${email} and org_id = ${orgId!}
returning *
`

Expand All @@ -189,8 +200,6 @@ auth.get("/join-data", async (ctx: Context) => {
select name, plan from org where id = ${orgId}
`

console.log(org)

const [orgUserCountResult] = await sql`
select count(*) from account where org_id = ${orgId}
`
Expand Down Expand Up @@ -272,7 +281,7 @@ auth.post("/request-password-reset", async (ctx: Context) => {

await sql`update account set recovery_token = ${token} where id = ${user.id}`

const link = `${process.env.NEXT_PUBLIC_APP_URL}/reset-password?token=${token}`
const link = `${process.env.APP_URL}/reset-password?token=${token}`

await sendEmail(RESET_PASSWORD(email, link))

Expand Down
15 changes: 7 additions & 8 deletions packages/backend/src/api/v1/auth/saml.ts
Expand Up @@ -15,8 +15,6 @@ const route = new Router({
prefix: "/saml/:orgId",
})

const BASE_URL = process.env.SAML_BASE_URL || process.env.API_URL

// This function generates a secure, one-time-use token
export async function generateOneTimeToken(): Promise<string> {
// Generate a 32-byte random buffer
Expand All @@ -37,9 +35,9 @@ function getSpMetadata(orgId: string) {
return `<?xml version="1.0"?>
<EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" entityID="${process.env.SAML_ENTITY_ID || "urn:lunary.ai:saml:sp"}">
<SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="${BASE_URL}/auth/saml/${orgId}/slo" />
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="${process.env.API_URL}/auth/saml/${orgId}/slo" />
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
<AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="${BASE_URL}/auth/saml/${orgId}/acs" index="1" />
<AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="${process.env.API_URL}/auth/saml/${orgId}/acs" index="1" />
</SPSSODescriptor>
<Organization>
<OrganizationName xml:lang="en-US">Lunary LLC</OrganizationName>
Expand Down Expand Up @@ -94,7 +92,7 @@ function parseAttributes(attributes: any) {
route.get("/success", async (ctx: Context) => {
const { orgId } = ctx.params as { orgId: string }

ctx.redirect(process.env.NEXT_PUBLIC_APP_URL!)
ctx.redirect(process.env.APP_URL!)
})

// Returns the Service Provider metadata
Expand All @@ -116,6 +114,7 @@ route.post("/download-idp-xml", async (ctx: Context) => {

await sql`update org set saml_idp_xml = ${xml} where id = ${orgId}`

ctx.body = { success: true }
ctx.status = 201
})

Expand Down Expand Up @@ -146,10 +145,10 @@ route.post("/acs", async (ctx: Context) => {

const { email, name } = parseAttributes(attributes)

const onetimeToken = await generateOneTimeToken()
const singleUseToken = await generateOneTimeToken()

const [account] =
await sql`update account set ${sql({ name, onetimeToken, lastLoginAt: new Date() })} where email = ${email} and org_id = ${orgId} returning *`
await sql`update account set ${sql({ name, singleUseToken, lastLoginAt: new Date() })} where email = ${email} and org_id = ${orgId} returning *`

if (!account) {
ctx.throw(
Expand All @@ -159,7 +158,7 @@ route.post("/acs", async (ctx: Context) => {
}

// Redirect with an one-time token that can be exchanged for an auth token
ctx.redirect(`${process.env.NEXT_PUBLIC_APP_URL!}/login?ott=${onetimeToken}`)
ctx.redirect(`${process.env.APP_URL!}/login?ott=${singleUseToken}`)
})

route.post("/slo", async (ctx: Context) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/api/v1/orgs.ts
Expand Up @@ -94,7 +94,7 @@ orgs.get("/billing-portal", async (ctx: Context) => {

const session = await stripe.billingPortal.sessions.create({
customer: org.stripeCustomer,
return_url: `${process.env.NEXT_PUBLIC_APP_URL}/billing`,
return_url: `${process.env.APP_URL}/billing`,
})

ctx.body = { url: session.url }
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/api/v1/users.ts
Expand Up @@ -113,7 +113,7 @@ users.get("/verify-email", async (ctx: Context) => {

await sendEmail(WELCOME_EMAIL(email, name, id))
// redirect to home page
ctx.redirect(process.env.NEXT_PUBLIC_APP_URL!)
ctx.redirect(process.env.APP_URL!)
})

users.post("/send-verification", async (ctx: Context) => {
Expand Down Expand Up @@ -205,7 +205,7 @@ users.post("/", checkAccess("teamMembers", "create"), async (ctx: Context) => {
`

if (!org.samlEnabled) {
const link = `${process.env.NEXT_PUBLIC_APP_URL}/join?token=${token}`
const link = `${process.env.APP_URL}/join?token=${token}`
await sendEmail(INVITE_EMAIL(email, org.name, link))
}

Expand Down
67 changes: 0 additions & 67 deletions packages/backend/src/checks/ai/ner.ts

This file was deleted.

0 comments on commit 67eaefe

Please sign in to comment.