mirror of
https://github.com/get-drexa/drive.git
synced 2025-11-30 21:41:39 +00:00
feat[convex]: api key auth support
This commit is contained in:
4
packages/convex/_generated/api.d.ts
vendored
4
packages/convex/_generated/api.d.ts
vendored
@@ -8,6 +8,7 @@
|
|||||||
* @module
|
* @module
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type * as apikey from "../apikey.js";
|
||||||
import type * as auth from "../auth.js";
|
import type * as auth from "../auth.js";
|
||||||
import type * as betterauth__generated_api from "../betterauth/_generated/api.js";
|
import type * as betterauth__generated_api from "../betterauth/_generated/api.js";
|
||||||
import type * as betterauth__generated_server from "../betterauth/_generated/server.js";
|
import type * as betterauth__generated_server from "../betterauth/_generated/server.js";
|
||||||
@@ -19,6 +20,7 @@ import type * as files from "../files.js";
|
|||||||
import type * as filesystem from "../filesystem.js";
|
import type * as filesystem from "../filesystem.js";
|
||||||
import type * as functions from "../functions.js";
|
import type * as functions from "../functions.js";
|
||||||
import type * as http from "../http.js";
|
import type * as http from "../http.js";
|
||||||
|
import type * as model_apikey from "../model/apikey.js";
|
||||||
import type * as model_directories from "../model/directories.js";
|
import type * as model_directories from "../model/directories.js";
|
||||||
import type * as model_files from "../model/files.js";
|
import type * as model_files from "../model/files.js";
|
||||||
import type * as model_filesystem from "../model/filesystem.js";
|
import type * as model_filesystem from "../model/filesystem.js";
|
||||||
@@ -43,6 +45,7 @@ import type {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
declare const fullApi: ApiFromModules<{
|
declare const fullApi: ApiFromModules<{
|
||||||
|
apikey: typeof apikey;
|
||||||
auth: typeof auth;
|
auth: typeof auth;
|
||||||
"betterauth/_generated/api": typeof betterauth__generated_api;
|
"betterauth/_generated/api": typeof betterauth__generated_api;
|
||||||
"betterauth/_generated/server": typeof betterauth__generated_server;
|
"betterauth/_generated/server": typeof betterauth__generated_server;
|
||||||
@@ -54,6 +57,7 @@ declare const fullApi: ApiFromModules<{
|
|||||||
filesystem: typeof filesystem;
|
filesystem: typeof filesystem;
|
||||||
functions: typeof functions;
|
functions: typeof functions;
|
||||||
http: typeof http;
|
http: typeof http;
|
||||||
|
"model/apikey": typeof model_apikey;
|
||||||
"model/directories": typeof model_directories;
|
"model/directories": typeof model_directories;
|
||||||
"model/files": typeof model_files;
|
"model/files": typeof model_files;
|
||||||
"model/filesystem": typeof model_filesystem;
|
"model/filesystem": typeof model_filesystem;
|
||||||
|
|||||||
14
packages/convex/apikey.ts
Normal file
14
packages/convex/apikey.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { verifyApiKey as _verifyApiKey, parseApiKey } from "@drexa/auth"
|
||||||
|
import { WebCryptoSha256Hasher } from "@drexa/auth/hasher"
|
||||||
|
import { v } from "convex/values"
|
||||||
|
import { query } from "./_generated/server"
|
||||||
|
import * as ApiKey from "./model/apikey"
|
||||||
|
|
||||||
|
export const verifyApiKey = query({
|
||||||
|
args: {
|
||||||
|
unhashedKey: v.string(),
|
||||||
|
},
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
return await ApiKey.verifyApiKey(ctx, args.unhashedKey)
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { createClient, type GenericCtx } from "@convex-dev/better-auth"
|
import { createClient, type GenericCtx } from "@convex-dev/better-auth"
|
||||||
import { convex, crossDomain } from "@convex-dev/better-auth/plugins"
|
import { convex, crossDomain } from "@convex-dev/better-auth/plugins"
|
||||||
import { betterAuth } from "better-auth"
|
|
||||||
import { components } from "@fileone/convex/api"
|
import { components } from "@fileone/convex/api"
|
||||||
import type { DataModel } from "@fileone/convex/dataModel"
|
import type { DataModel } from "@fileone/convex/dataModel"
|
||||||
|
import { betterAuth } from "better-auth"
|
||||||
import authSchema from "./betterauth/schema"
|
import authSchema from "./betterauth/schema"
|
||||||
|
|
||||||
const siteUrl = process.env.SITE_URL!
|
const siteUrl = process.env.SITE_URL!
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
|
import type { DataModel } from "@fileone/convex/dataModel"
|
||||||
|
import type { MutationCtx, QueryCtx } from "@fileone/convex/server"
|
||||||
|
import { mutation, query } from "@fileone/convex/server"
|
||||||
import type {
|
import type {
|
||||||
DocumentByName,
|
DocumentByName,
|
||||||
TableNamesInDataModel,
|
TableNamesInDataModel,
|
||||||
UserIdentity,
|
UserIdentity,
|
||||||
} from "convex/server"
|
} from "convex/server"
|
||||||
import type { GenericId } from "convex/values"
|
import { type GenericId, v } from "convex/values"
|
||||||
import {
|
import {
|
||||||
customCtx,
|
customCtx,
|
||||||
customMutation,
|
customMutation,
|
||||||
customQuery,
|
customQuery,
|
||||||
} from "convex-helpers/server/customFunctions"
|
} from "convex-helpers/server/customFunctions"
|
||||||
import type { DataModel } from "@fileone/convex/dataModel"
|
import * as ApiKey from "./model/apikey"
|
||||||
import type { MutationCtx, QueryCtx } from "@fileone/convex/server"
|
|
||||||
import { mutation, query } from "@fileone/convex/server"
|
|
||||||
import { type AuthUser, userIdentityOrThrow, userOrThrow } from "./model/user"
|
import { type AuthUser, userIdentityOrThrow, userOrThrow } from "./model/user"
|
||||||
|
import * as Err from "./shared/error"
|
||||||
|
|
||||||
export type AuthenticatedQueryCtx = QueryCtx & {
|
export type AuthenticatedQueryCtx = QueryCtx & {
|
||||||
user: AuthUser
|
user: AuthUser
|
||||||
@@ -50,6 +52,21 @@ export const authenticatedMutation = customMutation(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom mutation that requires api key authentication for a mutation.
|
||||||
|
*/
|
||||||
|
export const apiKeyAuthenticatedMutation = customMutation(mutation, {
|
||||||
|
args: {
|
||||||
|
apiKey: v.string(),
|
||||||
|
},
|
||||||
|
input: async (ctx, args) => {
|
||||||
|
if (!(await ApiKey.verifyApiKey(ctx, args.apiKey))) {
|
||||||
|
throw Err.create(Err.Code.Unauthenticated, "Invalid API key")
|
||||||
|
}
|
||||||
|
return { ctx, args }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a document by its id and checks if the user is authorized to access it
|
* Gets a document by its id and checks if the user is authorized to access it
|
||||||
*
|
*
|
||||||
|
|||||||
30
packages/convex/model/apikey.ts
Normal file
30
packages/convex/model/apikey.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import {
|
||||||
|
verifyApiKey as _verifyApiKey,
|
||||||
|
parseApiKey,
|
||||||
|
WebCryptoSha256Hasher,
|
||||||
|
} from "@drexa/auth"
|
||||||
|
import type { MutationCtx, QueryCtx } from "../_generated/server"
|
||||||
|
|
||||||
|
export async function verifyApiKey(
|
||||||
|
ctx: MutationCtx | QueryCtx,
|
||||||
|
unhashedKey: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const parsedKey = parseApiKey(unhashedKey)
|
||||||
|
if (!parsedKey) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiKey = await ctx.db
|
||||||
|
.query("apiKeys")
|
||||||
|
.withIndex("byPublicId", (q) => q.eq("publicId", parsedKey.prefix))
|
||||||
|
.first()
|
||||||
|
if (!apiKey) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _verifyApiKey({
|
||||||
|
keyToBeVerified: parsedKey.unhashedKey,
|
||||||
|
hashedKey: apiKey.hashedKey,
|
||||||
|
hasher: new WebCryptoSha256Hasher(),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -14,7 +14,9 @@
|
|||||||
"./shared/*": "./shared/*"
|
"./shared/*": "./shared/*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fileone/path": "workspace:*"
|
"@drexa/auth": "workspace:*",
|
||||||
|
"@fileone/path": "workspace:*",
|
||||||
|
"hash-wasm": "^4.12.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ const schema = defineSchema({
|
|||||||
"name",
|
"name",
|
||||||
"deletedAt",
|
"deletedAt",
|
||||||
]),
|
]),
|
||||||
|
apiKeys: defineTable({
|
||||||
|
publicId: v.string(),
|
||||||
|
hashedKey: v.string(),
|
||||||
|
createdAt: v.number(),
|
||||||
|
updatedAt: v.number(),
|
||||||
|
expiresAt: v.optional(v.number()),
|
||||||
|
}).index("byPublicId", ["publicId"]),
|
||||||
})
|
})
|
||||||
|
|
||||||
export default schema
|
export default schema
|
||||||
|
|||||||
Reference in New Issue
Block a user