Skip to content

Commit

Permalink
Add clients endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
markusahlstrand committed Nov 4, 2024
1 parent 8244aa2 commit 4064c4d
Show file tree
Hide file tree
Showing 10 changed files with 501 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/gold-bulldogs-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"authhero": minor
---

Add clients endpoints
22 changes: 22 additions & 0 deletions packages/authhero/src/helpers/sort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export function parseSort(sort?: string):
| undefined
| {
sort_by: string;
sort_order: "asc" | "desc";
} {
if (!sort) {
return undefined;
}

const [sort_by, orderString] = sort.split(":");
const sort_order = orderString === "1" ? "asc" : "desc";

if (!sort_by || !sort_order) {
return undefined;
}

return {
sort_by,
sort_order,
};
}
4 changes: 2 additions & 2 deletions packages/authhero/src/management-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { addDataHooks } from "./hooks";
// import { userRoutes } from "./routes/management-api/users";
// import { keyRoutes } from "./routes/management-api/keys";
// import { usersByEmailRoutes } from "./routes/management-api/users-by-email";
// import { applicationRoutes } from "./routes/management-api/applications";
import { clientRoutes } from "./routes/management-api/clients";
import { tenantRoutes } from "./routes/management-api/tenants";
// import { logRoutes } from "./routes/management-api/logs";
// import { hooksRoutes } from "./routes/management-api/hooks";
Expand Down Expand Up @@ -35,7 +35,7 @@ export default function create(params: CreateAuthParams) {
// .route("/api/v2/users", userRoutes)
// .route("/api/v2/keys/signing", keyRoutes)
// .route("/api/v2/users-by-email", usersByEmailRoutes)
// .route("/api/v2/applications", applicationRoutes)
.route("/api/v2/clients", clientRoutes)
.route("/api/v2/tenants", tenantRoutes);
// .route("/api/v2/logs", logRoutes)
// .route("/api/v2/hooks", hooksRoutes)
Expand Down
288 changes: 288 additions & 0 deletions packages/authhero/src/routes/management-api/clients.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
import {
applicationSchema,
applicationInsertSchema,
totalsSchema,
} from "@authhero/adapter-interfaces";
// import { headers } from "../../constants";
import { Bindings } from "../../types";
import { HTTPException } from "hono/http-exception";
import { nanoid } from "nanoid";
import { auth0QuerySchema } from "../../types/auth0/Query";
import { parseSort } from "../../helpers/sort";
// import authenticationMiddleware from "../../middlewares/authentication";

const applicationWithTotalsSchema = totalsSchema.extend({
clients: z.array(applicationSchema),
});

export const clientRoutes = new OpenAPIHono<{ Bindings: Bindings }>()
// --------------------------------
// GET /clients
// --------------------------------
.openapi(
createRoute({
tags: ["clients"],
method: "get",
path: "/",
request: {
query: auth0QuerySchema,
headers: z.object({
"tenant-id": z.string(),
}),
},
// middleware: [authenticationMiddleware({ scopes: ["auth:read"] })],
security: [
{
Bearer: ["auth:read"],
},
],
responses: {
200: {
content: {
"application/json": {
schema: z.union([
applicationWithTotalsSchema,
z.array(applicationSchema),
]),
},
},
description: "List of clients",
},
},
}),
async (ctx) => {
const { "tenant-id": tenant_id } = ctx.req.valid("header");
const { page, per_page, include_totals, sort, q } =
ctx.req.valid("query");

const result = await ctx.env.data.applications.list(tenant_id, {
page,
per_page,
include_totals,
sort: parseSort(sort),
q,
});

const clients = result.applications;

if (include_totals) {
// TODO: this should be supported by the adapter
return ctx.json({
clients,
start: 0,
limit: 10,
length: clients.length,
});
}

return ctx.json(clients);
},
)
// --------------------------------
// GET /clients/:id
// --------------------------------
.openapi(
createRoute({
tags: ["clients"],
method: "get",
path: "/{id}",
request: {
params: z.object({
id: z.string(),
}),
headers: z.object({
"tenant-id": z.string(),
}),
},
// middleware: [authenticationMiddleware({ scopes: ["auth:read"] })],
security: [
{
Bearer: ["auth:read"],
},
],
responses: {
200: {
content: {
"application/json": {
schema: applicationSchema,
},
},
description: "An application",
},
},
}),
async (ctx) => {
const { "tenant-id": tenant_id } = ctx.req.valid("header");
const { id } = ctx.req.valid("param");

// Workaround until the adapter is fixed
// const application = await ctx.env.data.clients.get(tenant_id, id);
const clients = await ctx.env.data.applications.list(tenant_id, {
page: 1,
per_page: 0,
include_totals: false,
});
const application = clients.applications.find((a) => a.id === id);

if (!application) {
throw new HTTPException(404);
}

return ctx.json(application, {
// headers,
});
},
)
// --------------------------------
// DELETE /clients/:id
// --------------------------------
.openapi(
createRoute({
tags: ["clients"],
method: "delete",
path: "/{id}",
request: {
params: z.object({
id: z.string(),
}),
headers: z.object({
"tenant-id": z.string(),
}),
},
// middleware: [authenticationMiddleware({ scopes: ["auth:write"] })],
security: [
{
Bearer: ["auth:write"],
},
],
responses: {
200: {
description: "Status",
},
},
}),
async (ctx) => {
const { "tenant-id": tenant_id } = ctx.req.valid("header");
const { id } = ctx.req.valid("param");

const result = await ctx.env.data.applications.remove(tenant_id, id);
if (!result) {
throw new HTTPException(404, { message: "Application not found" });
}

return ctx.text("OK");
},
)
// --------------------------------
// PATCH /clients/:id
// --------------------------------
.openapi(
createRoute({
tags: ["clients"],
method: "patch",
path: "/{id}",
request: {
body: {
content: {
"application/json": {
schema: z.object(applicationInsertSchema.shape).partial(),
},
},
},
params: z.object({
id: z.string(),
}),
headers: z.object({
"tenant-id": z.string(),
}),
},
// middleware: [authenticationMiddleware({ scopes: ["auth:write"] })],
security: [
{
Bearer: ["auth:write"],
},
],
responses: {
200: {
content: {
"application/json": {
schema: applicationSchema,
},
},
description: "The update application",
},
},
}),
async (ctx) => {
const { "tenant-id": tenant_id } = ctx.req.valid("header");
const { id } = ctx.req.valid("param");
const body = ctx.req.valid("json");

const applicationUpdate = body;

await ctx.env.data.applications.update(tenant_id, id, applicationUpdate);
const application = await ctx.env.data.applications.get(tenant_id, id);

if (!application) {
throw new HTTPException(404, { message: "Application not found" });
}

return ctx.json(application);
},
)
// --------------------------------
// POST /clients
// --------------------------------
.openapi(
createRoute({
tags: ["clients"],
method: "post",
path: "/",
request: {
body: {
content: {
"application/json": {
schema: z.object(applicationInsertSchema.shape),
},
},
},
headers: z.object({
"tenant-id": z.string(),
}),
},
// middleware: [authenticationMiddleware({ scopes: ["auth:write"] })],
security: [
{
Bearer: ["auth:write"],
},
],
responses: {
201: {
content: {
"application/json": {
schema: z.object(applicationSchema.shape),
},
},
description: "An application",
},
},
}),
async (ctx) => {
const { "tenant-id": tenant_id } = ctx.req.valid("header");
const body = ctx.req.valid("json");

const applicationUpdate = {
...body,
id: body.id || nanoid(),
client_secret: body.client_secret || nanoid(),
};

const application = await ctx.env.data.applications.create(
tenant_id,
applicationUpdate,
);

return ctx.json(application, { status: 201 });
},
);
File renamed without changes.
13 changes: 13 additions & 0 deletions packages/authhero/src/types/auth0/Totals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { z } from "zod";

export const totalsSchema = z.object({
start: z.number(),
limit: z.number(),
length: z.number(),
});

export interface Totals {
start: number;
limit: number;
length: number;
}
24 changes: 24 additions & 0 deletions packages/authhero/src/types/auth0/UserResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { BaseUser, baseUserSchema } from "@authhero/adapter-interfaces";
import { z } from "zod";

export interface PostUsersBody extends BaseUser {
password?: string;
// Whether this user will receive a verification email after creation (true) or no email (false). Overrides behavior of email_verified parameter.
verify_email?: boolean;
username?: string;
connection?: string;
email_verified?: boolean;
}

export const userResponseSchema = baseUserSchema
.extend({
email: z.string(),
login_count: z.number(),
multifactor: z.array(z.string()).optional(),
last_ip: z.string().optional(),
last_login: z.string().optional(),
user_id: z.string(),
})
.catchall(z.any());

export type UserResponse = z.infer<typeof userResponseSchema>;
3 changes: 3 additions & 0 deletions packages/authhero/src/types/auth0/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./Totals";
export * from "./UserResponse";
export * from "./Query";
2 changes: 1 addition & 1 deletion packages/authhero/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from "./Variables";
export * from "./Bindings";
export * from "./Query";
export * from "./auth0";
Loading

0 comments on commit 4064c4d

Please sign in to comment.