diff --git a/.env.example b/.env.example index 3f48cfc..0771b11 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,10 @@ GOOGLE_CLIENT_ID="" GOOGLE_CLIENT_SECRET="" GITHUB_ID="" GITHUB_SECRET="" +KEYCLOAK_ID="" # Keycloak ClientID +KEYCLOAK_SECRET="" # Keycloak ClientSecret(require client authentication) +KEYCLOAK_ISSUER="" # Keycloak realms url(https://example.com/realms/master) + # AWS S3 AWS_ENDPOINT="" @@ -15,6 +19,7 @@ AWS_REGION="" AWS_KEY_ID="" AWS_SECRET_ACCESS_KEY="" AWS_BUCKET_NAME="" +AWS_FORCE_PATH_STYLE="false" # Change to true when using Minio # QStash QSTASH_CURRENT_SIGNING_KEY="" @@ -38,4 +43,4 @@ POSTHOG_PROXY_PATH="ioafe" # redis for ratelimiting UPSTASH_REDIS_REST_URL="" -UPSTASH_REDIS_REST_TOKEN="" \ No newline at end of file +UPSTASH_REDIS_REST_TOKEN="" diff --git a/src/env.mjs b/src/env.mjs index 07e2560..f59da3b 100644 --- a/src/env.mjs +++ b/src/env.mjs @@ -23,11 +23,15 @@ const server = z.object({ GOOGLE_CLIENT_SECRET: z.string().nullish(), GITHUB_ID: z.string(), GITHUB_SECRET: z.string(), + KEYCLOAK_ID: z.string().nullish(), + KEYCLOAK_SECRET: z.string().nullish(), + KEYCLOAK_ISSUER: z.string().nullish(), AWS_ENDPOINT: z.string(), AWS_REGION: z.string(), AWS_KEY_ID: z.string(), AWS_SECRET_ACCESS_KEY: z.string(), AWS_BUCKET_NAME: z.string(), + AWS_FORCE_PATH_STYLE: z.string().nullish(), STRIPE_SECRET_KEY: z.string().nullish(), STRIPE_WEBHOOK_SECRET: z.string().nullish(), STRIPE_MONTHLY_PRICE_ID: z.string().nullish(), @@ -66,11 +70,15 @@ const processEnv = { GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET, GITHUB_ID: process.env.GITHUB_ID, GITHUB_SECRET: process.env.GITHUB_SECRET, + KEYCLOAK_ID: process.env.KEYCLOAK_ID, + KEYCLOAK_SECRET: process.env.KEYCLOAK_SECRET, + KEYCLOAK_ISSUER: process.env.KEYCLOAK_ISSUER, AWS_ENDPOINT: process.env.AWS_ENDPOINT, AWS_REGION: process.env.AWS_REGION, AWS_KEY_ID: process.env.AWS_KEY_ID, AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY, AWS_BUCKET_NAME: process.env.AWS_BUCKET_NAME, + AWS_FORCE_PATH_STYLE: process.env.AWS_FORCE_PATH_STYLE, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY, diff --git a/src/server/auth.ts b/src/server/auth.ts index bd67ae0..8626c6c 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -6,6 +6,7 @@ import { } from "next-auth"; import GoogleProvider from "next-auth/providers/google"; import GitHubProvider from "next-auth/providers/github"; +import KeycloakProvider from "next-auth/providers/keycloak"; import { PrismaAdapter } from "@next-auth/prisma-adapter"; import { env } from "~/env.mjs"; import { prisma } from "~/server/db"; @@ -32,6 +33,18 @@ declare module "next-auth" { } } +const adapter = PrismaAdapter(prisma); + +const _linkAccount = adapter.linkAccount; + +// Required when using keycloak. +// keycloak always sends the refresh_expires_in attribute. +// prisma is extended to handle refresh_expires_in. +adapter.linkAccount = (account) => { + const { 'not-before-policy': _, refresh_expires_in, ...data } = account; + return _linkAccount(data); +}; + /** * Options for NextAuth.js used to configure adapters, providers, callbacks, etc. * @@ -48,7 +61,7 @@ export const authOptions: NextAuthOptions = { }, }), }, - adapter: PrismaAdapter(prisma), + adapter: adapter, providers: [ ...(!!env.GOOGLE_CLIENT_ID && !!env.GOOGLE_CLIENT_SECRET ? [ @@ -58,10 +71,23 @@ export const authOptions: NextAuthOptions = { }), ] : []), - GitHubProvider({ - clientId: env.GITHUB_ID, - clientSecret: env.GITHUB_SECRET, - }), + ...(!!env.GITHUB_ID && !!env.GITHUB_SECRET + ? [ + GitHubProvider({ + clientId: env.GITHUB_ID, + clientSecret: env.GITHUB_SECRET, + }), + ] + : []), + ...(!!env.KEYCLOAK_ID && !!env.KEYCLOAK_SECRET && !!env.KEYCLOAK_ISSUER + ? [ + KeycloakProvider({ + clientId: env.KEYCLOAK_ID, + clientSecret: env.KEYCLOAK_SECRET, + issuer: env.KEYCLOAK_ISSUER, + }), + ] + : []), /** * ...add more providers here. * diff --git a/src/server/aws/s3.ts b/src/server/aws/s3.ts index 12a3c4f..f89bd70 100644 --- a/src/server/aws/s3.ts +++ b/src/server/aws/s3.ts @@ -1,6 +1,11 @@ import { S3 } from "@aws-sdk/client-s3"; import { env } from "~/env.mjs"; +// Isn't it better code? where is the right place to write? +function toBoolean(booleanStr: string): boolean { + return booleanStr.toLowerCase() === "true"; +} + export const s3 = new S3({ endpoint: env.AWS_ENDPOINT, region: env.AWS_REGION, @@ -8,4 +13,8 @@ export const s3 = new S3({ accessKeyId: env.AWS_KEY_ID, secretAccessKey: env.AWS_SECRET_ACCESS_KEY, }, + // A necessary option when using Minio. false is the default behavior. + // If false, the domain will be changed to [bucket name].example.com. + // If true, appends the bucket to the path, like example.com/[bucket name]. + "forcePathStyle": toBoolean(env.AWS_FORCE_PATH_STYLE), });