2025-02-05 21:49:39 +01:00
import { z } from "@hono/zod-openapi" ;
2024-09-25 12:31:35 +02:00
import { Hooks , Plugin } from "@versia/kit" ;
2024-10-24 18:48:11 +02:00
import { User } from "@versia/kit/db" ;
2024-10-24 18:18:39 +02:00
import chalk from "chalk" ;
2024-12-18 20:42:40 +01:00
import { getCookie } from "hono/cookie" ;
2024-11-25 13:09:28 +01:00
import { jwtVerify } from "jose" ;
import { JOSEError , JWTExpired } from "jose/errors" ;
2024-12-30 18:00:23 +01:00
import { ApiError } from "~/classes/errors/api-error.ts" ;
2024-11-25 13:09:28 +01:00
import { RolePermissions } from "~/drizzle/schema.ts" ;
2024-10-04 15:22:48 +02:00
import authorizeRoute from "./routes/authorize.ts" ;
2024-10-07 12:52:22 +02:00
import jwksRoute from "./routes/jwks.ts" ;
2024-10-11 15:15:06 +02:00
import ssoLoginCallbackRoute from "./routes/oauth/callback.ts" ;
2024-10-04 15:22:48 +02:00
import tokenRevokeRoute from "./routes/oauth/revoke.ts" ;
2024-10-11 14:39:25 +02:00
import ssoLoginRoute from "./routes/oauth/sso.ts" ;
2024-10-04 15:22:48 +02:00
import tokenRoute from "./routes/oauth/token.ts" ;
import ssoIdRoute from "./routes/sso/:id/index.ts" ;
import ssoRoute from "./routes/sso/index.ts" ;
2024-08-29 20:32:04 +02:00
2024-11-25 13:09:28 +01:00
const configSchema = z . object ( {
forced : z.boolean ( ) . default ( false ) ,
allow_registration : z.boolean ( ) . default ( true ) ,
providers : z
. array (
z . object ( {
name : z.string ( ) . min ( 1 ) ,
id : z.string ( ) . min ( 1 ) ,
url : z.string ( ) . min ( 1 ) ,
client_id : z.string ( ) . min ( 1 ) ,
client_secret : z.string ( ) . min ( 1 ) ,
icon : z.string ( ) . min ( 1 ) . optional ( ) ,
2024-10-24 18:18:39 +02:00
} ) ,
2024-11-25 13:09:28 +01:00
)
. default ( [ ] ) ,
keys : z
. object ( {
public : z
. string ( )
. min ( 1 )
. transform ( async ( v ) = > {
try {
return await crypto . subtle . importKey (
"spki" ,
Buffer . from ( v , "base64" ) ,
"Ed25519" ,
true ,
[ "verify" ] ,
) ;
} catch {
throw new Error (
"Public key at oidc.keys.public is invalid" ,
) ;
}
} ) ,
private : z
. string ( )
. min ( 1 )
. transform ( async ( v ) = > {
try {
return await crypto . subtle . importKey (
"pkcs8" ,
Buffer . from ( v , "base64" ) ,
"Ed25519" ,
true ,
[ "sign" ] ,
) ;
} catch {
throw new Error (
"Private key at oidc.keys.private is invalid" ,
) ;
}
} ) ,
} )
. optional ( )
. transform ( async ( v , ctx ) = > {
if ( ! ( v ? . private && v ? . public ) ) {
const { public_key , private_key } = await User . generateKeys ( ) ;
ctx . addIssue ( {
code : z.ZodIssueCode.custom ,
message : ` Keys are missing, please add the following to your config: \ n \ nkeys.public: ${ chalk . gray ( public_key ) } \ nkeys.private: ${ chalk . gray ( private_key ) }
` ,
} ) ;
}
return v as Exclude < typeof v , undefined > ;
} ) ,
} ) ;
const plugin = new Plugin ( configSchema ) ;
2024-08-29 20:32:04 +02:00
2024-09-30 13:42:12 +02:00
// Test hook for screenshots
2024-08-29 20:32:04 +02:00
plugin . registerHandler ( Hooks . Response , ( req ) = > {
console . info ( "Request received:" , req ) ;
return req ;
} ) ;
2024-09-30 13:42:12 +02:00
2024-08-29 20:32:04 +02:00
authorizeRoute ( plugin ) ;
2024-09-24 14:42:39 +02:00
ssoRoute ( plugin ) ;
2024-09-25 12:31:35 +02:00
ssoIdRoute ( plugin ) ;
2024-09-30 13:42:12 +02:00
tokenRoute ( plugin ) ;
tokenRevokeRoute ( plugin ) ;
2024-10-07 12:52:22 +02:00
jwksRoute ( plugin ) ;
2024-10-11 14:39:25 +02:00
ssoLoginRoute ( plugin ) ;
2024-10-11 15:15:06 +02:00
ssoLoginCallbackRoute ( plugin ) ;
2024-08-29 20:32:04 +02:00
2024-11-25 13:09:28 +01:00
plugin . registerRoute ( "/admin/*" , ( app ) = > {
// Check for JWT when accessing the admin panel
app . use ( "/admin/*" , async ( context , next ) = > {
const jwtCookie = getCookie ( context , "jwt" ) ;
if ( ! jwtCookie ) {
2024-12-30 18:00:23 +01:00
throw new ApiError ( 401 , "Missing JWT cookie" ) ;
2024-11-25 13:09:28 +01:00
}
const { keys } = context . get ( "pluginConfig" ) ;
const result = await jwtVerify ( jwtCookie , keys . public , {
algorithms : [ "EdDSA" ] ,
issuer : new URL ( context . get ( "config" ) . http . base_url ) . origin ,
} ) . catch ( ( error ) = > {
if ( error instanceof JOSEError ) {
return error ;
}
throw error ;
} ) ;
if ( result instanceof JOSEError ) {
if ( result instanceof JWTExpired ) {
2024-12-30 18:00:23 +01:00
throw new ApiError ( 401 , "JWT has expired" ) ;
2024-11-25 13:09:28 +01:00
}
2024-12-30 18:00:23 +01:00
throw new ApiError ( 401 , "Invalid JWT" ) ;
2024-11-25 13:09:28 +01:00
}
const {
payload : { sub } ,
} = result ;
if ( ! sub ) {
2024-12-30 18:00:23 +01:00
throw new ApiError ( 401 , "Invalid JWT (no sub)" ) ;
2024-11-25 13:09:28 +01:00
}
const user = await User . fromId ( sub ) ;
if ( ! user ? . hasPermission ( RolePermissions . ManageInstanceFederation ) ) {
2024-12-30 18:00:23 +01:00
throw new ApiError (
2024-11-25 13:09:28 +01:00
403 ,
2024-12-30 18:00:23 +01:00
` Missing ' ${ RolePermissions . ManageInstanceFederation } ' permission ` ,
2024-11-25 13:09:28 +01:00
) ;
}
await next ( ) ;
} ) ;
} ) ;
2024-08-29 20:32:04 +02:00
export type PluginType = typeof plugin ;
export default plugin ;