Skip to content

Commit

Permalink
Merge branch 'main' of github.com:lunary-ai/lunary
Browse files Browse the repository at this point in the history
  • Loading branch information
hughcrt committed Mar 25, 2024
2 parents bf73aac + eea43cf commit 914b310
Show file tree
Hide file tree
Showing 43 changed files with 2,346 additions and 849 deletions.
32 changes: 16 additions & 16 deletions .github/workflows/test-build-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,19 @@ jobs:
lunary/lunary-ee:rev-${{ steps.date.outputs.date }}-${{ steps.commit.outputs.hash }}
platforms: linux/amd64

deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Deploy to Production
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PRODUCTION_IP_ADDRESS }}
username: root
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 3855
script: |
cd /opt/lunary
docker compose pull
docker compose down
docker compose up -d
# deploy:
# needs: build-and-push
# runs-on: ubuntu-latest
# steps:
# - name: Deploy to Production
# uses: appleboy/ssh-action@master
# with:
# host: ${{ secrets.PRODUCTION_IP_ADDRESS }}
# username: root
# key: ${{ secrets.SSH_PRIVATE_KEY }}
# port: 3855
# script: |
# cd /opt/lunary
# docker compose pull
# docker compose down
# docker compose up -d
9 changes: 5 additions & 4 deletions package-lock.json

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

37 changes: 37 additions & 0 deletions packages/backend/src/access-control/authorization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Next } from "koa"
import Context from "../utils/koa"
import { roles } from "./roles"

type PermissionType =
| "create"
| "read"
| "update"
| "delete"
| "list"
| "export"

// TODO: change the whole thing. A user as a field projectRoles, so it's better for modularity later
// TODO: attach to user its projectRoles
export function authorize(resource: string, permission: PermissionType) {
return async (ctx: Context, next: Next) => {
const projectId = ctx.params.projectId
const projectRole = ctx.state.user.projectRoles.find(
(pr) => pr.projectId === projectId,
)

if (!projectRole) {
ctx.throw(403, "Forbidden: No access to the project")
}

const hasPermission = projectRole.roles.some((role) => {
const rolePermissions = roles[role]?.permissions
return rolePermissions && rolePermissions[resource]?.[permission]
})

if (!hasPermission) {
ctx.throw(403, "Forbidden: Insufficient permissions")
}

await next()
}
}
84 changes: 84 additions & 0 deletions packages/backend/src/access-control/roles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
export const roles = {
member: {
value: "member",
name: "Member",
description: "Full access to most resources",
permissions: {
project: {
create: false,
read: true,
update: false,
delete: false,
list: false,
},
teamMembers: {
create: false,
read: true,
update: false,
delete: false,
list: true,
},
apiKeys: {
create: true,
read: true,
update: true,
delete: false,
list: true,
},
analytics: { read: true },
logs: {
create: true,
read: true,
update: true,
delete: true,
list: true,
export: true,
},
users: {
create: false,
read: true,
update: false,
delete: false,
list: true,
export: true,
},
prompts: {
create: true,
read: true,
update: true,
delete: true,
list: true,
run: true,
},
radars: {
create: true,
read: true,
update: true,
delete: true,
list: true,
},
datasets: {
create: true,
read: true,
update: true,
delete: true,
list: true,
},
checklists: {
create: true,
read: true,
update: true,
delete: true,
list: true,
},
evaluations: {
create: true,
read: true,
update: true,
delete: true,
list: true,
run: true,
},
},
},
}
45 changes: 37 additions & 8 deletions packages/backend/src/api/v1/analytics.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { checkAccess } from "@/src/utils/authorization"
import sql from "@/src/utils/db"
import Context from "@/src/utils/koa"
import Router from "koa-router"
Expand All @@ -6,10 +7,18 @@ const analytics = new Router({
prefix: "/analytics",
})

analytics.get("/tokens", async (ctx: Context) => {})
analytics.get("/tokens", async (ctx: Context) => {})
analytics.get(
"/tokens",
checkAccess("analytics", "read"),
async (ctx: Context) => {},
)
analytics.get(
"/tokens",
checkAccess("analytics", "read"),
async (ctx: Context) => {},
)

analytics.get("/cost", async (ctx) => {
analytics.get("/cost", checkAccess("analytics", "read"), async (ctx) => {
const { projectId } = ctx.state

await sql`
Expand All @@ -26,14 +35,34 @@ analytics.get("/cost", async (ctx) => {
`
})

analytics.get("/users/cost", async (ctx: Context) => {})
analytics.get(
"/users/cost",
checkAccess("analytics", "read"),
async (ctx: Context) => {},
)

analytics.get("/errors", async (ctx: Context) => {})
analytics.get(
"/errors",
checkAccess("analytics", "read"),
async (ctx: Context) => {},
)

analytics.get("/latency", async (ctx: Context) => {})
analytics.get(
"/latency",
checkAccess("analytics", "read"),
async (ctx: Context) => {},
)

analytics.get("/feedback/positive", async (ctx: Context) => {})
analytics.get(
"/feedback/positive",
checkAccess("analytics", "read"),
async (ctx: Context) => {},
)

analytics.get("/feedback/negative", async (ctx: Context) => {})
analytics.get(
"/feedback/negative",
checkAccess("analytics", "read"),
async (ctx: Context) => {},
)

export default analytics
62 changes: 49 additions & 13 deletions packages/backend/src/api/v1/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
verifyPassword,
} from "./utils"
import saml, { getLoginUrl } from "./saml"
import { jwtVerify } from "jose"

const auth = new Router({
prefix: "/auth",
Expand Down Expand Up @@ -51,6 +52,7 @@ auth.post("/signup", async (ctx: Context) => {
projectName: z.string().optional(),
employeeCount: z.string().optional(),
orgId: z.string().optional(),
token: z.string().optional(),
signupMethod: z.enum(["signup", "join"]),
})

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

if (orgName?.includes("https://") || name.includes("http://")) {
Expand All @@ -72,10 +75,6 @@ auth.post("/signup", async (ctx: Context) => {
const [existingUser] = await sql`
select * from account where lower(email) = lower(${email})
`
if (existingUser) {
ctx.throw(403, "User already exists")
}

if (signupMethod === "signup") {
const { user, org } = await sql.begin(async (sql) => {
const plan = process.env.DEFAULT_PLAN || "free"
Expand Down Expand Up @@ -106,6 +105,10 @@ auth.post("/signup", async (ctx: Context) => {
insert into project ${sql(newProject)} returning *
`

await sql`
insert into account_project ${sql({ accountId: user.id, projectId: project.id })}
`

const publicKey = {
type: "public",
projectId: project.id,
Expand Down Expand Up @@ -147,6 +150,11 @@ auth.post("/signup", async (ctx: Context) => {
ctx.body = { token }
return
} else if (signupMethod === "join") {
const { payload } = await verifyJwt(token!)
if (payload.email !== email) {
ctx.throw(403, "Wrong email")
}

const newUser = {
name,
passwordHash: await hashPassword(password),
Expand All @@ -155,19 +163,47 @@ auth.post("/signup", async (ctx: Context) => {
role: "member",
verified: true,
}
const [user] = await sql`insert into account ${sql(newUser)} returning *`

const token = await signJwt({
userId: user.id,
email: user.email,
orgId,
})
const [user] = await sql`
update account set
name = ${newUser.name},
password_hash = ${newUser.passwordHash},
verified = true,
single_use_token = null
where email = ${newUser.email}
returning *
`

ctx.body = { token }
ctx.body = {}
return
}
})

auth.get("/join-data", async (ctx: Context) => {
const token = z.string().parse(ctx.query.token)

const {
payload: { orgId },
} = await verifyJwt(token)

const [org] = await sql`
select name, plan from org where id = ${orgId}
`

console.log(org)

const [orgUserCountResult] = await sql`
select count(*) from account where org_id = ${orgId}
`
const orgUserCount = parseInt(orgUserCountResult.count, 10)

ctx.body = {
orgUserCount,
orgName: org?.name,
orgPlan: org?.plan,
orgId: orgId,
}
})

auth.post("/login", async (ctx: Context) => {
const bodySchema = z.object({
email: z.string().email().transform(sanitizeEmail),
Expand Down Expand Up @@ -282,7 +318,7 @@ auth.post("/exchange-token", async (ctx: Context) => {

// get account with onetime_token = token
const [account] = await sql`
update account set onetime_token = null where onetime_token = ${onetimeToken} returning *
update account set single_use_token = null where single_use_token = ${onetimeToken} returning *
`

if (!account) {
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/api/v1/auth/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ export async function authMiddleware(ctx: Context, next: Next) {
ctx.state.orgId = payload.orgId

if (ctx.state.projectId) {
// CHeck if user has access to project
// Check if user has access to project

const [project] = await sql`
select id from project where id = ${ctx.state.projectId} and org_id = ${ctx.state.orgId}
select * from account_project where account_id = ${ctx.state.userId} and project_id = ${ctx.state.projectId}
`

if (!project) {
Expand Down
Loading

0 comments on commit 914b310

Please sign in to comment.