feat: add Hanzo IAM OIDC SSO provider
- Add HanzoIAMProvider to NextAuth config (OIDC with userinfo endpoint) - Add "Sign in with Hanzo" button to login page (above passkey/Google) - Add IS_HANZO_IAM_ENABLED constant - Env vars: HANZO_IAM_URL, HANZO_IAM_CLIENT_ID, HANZO_IAM_CLIENT_SECRET - IAM app registered as app-captable in init_data.json
This commit is contained in:
+8
-1
@@ -58,4 +58,11 @@ yarn-error.log*
|
||||
|
||||
# Sentry Config File
|
||||
.env.sentry-build-plugin
|
||||
notes.md
|
||||
notes.md
|
||||
|
||||
|
||||
AGENTS.md
|
||||
CLAUDE.md
|
||||
GEMINI.md
|
||||
GROK.md
|
||||
QWEN.md
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SignInForm from "@/components/onboarding/signin";
|
||||
import { IS_GOOGLE_AUTH_ENABLED } from "@/constants/auth";
|
||||
import { IS_GOOGLE_AUTH_ENABLED, IS_HANZO_IAM_ENABLED } from "@/constants/auth";
|
||||
import { getServerComponentAuthSession } from "@/server/auth";
|
||||
import type { Metadata } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
@@ -19,5 +19,5 @@ export default async function SignIn() {
|
||||
return redirect("/onboarding");
|
||||
}
|
||||
|
||||
return <SignInForm isGoogleAuthEnabled={IS_GOOGLE_AUTH_ENABLED} />;
|
||||
return <SignInForm isGoogleAuthEnabled={IS_GOOGLE_AUTH_ENABLED} isHanzoIAMEnabled={IS_HANZO_IAM_ENABLED} />;
|
||||
}
|
||||
|
||||
@@ -35,9 +35,10 @@ const loginSchema = z.object({
|
||||
|
||||
interface LoginFormProps {
|
||||
isGoogleAuthEnabled: boolean;
|
||||
isHanzoIAMEnabled?: boolean;
|
||||
}
|
||||
|
||||
const SignInForm = ({ isGoogleAuthEnabled }: LoginFormProps) => {
|
||||
const SignInForm = ({ isGoogleAuthEnabled, isHanzoIAMEnabled }: LoginFormProps) => {
|
||||
const router = useRouter();
|
||||
const [isPasskeyLoading, setIsPasskeyLoading] = useState<boolean>(false);
|
||||
|
||||
@@ -108,11 +109,29 @@ const SignInForm = ({ isGoogleAuthEnabled }: LoginFormProps) => {
|
||||
await signIn("google", { callbackUrl: "/onboarding" });
|
||||
}
|
||||
|
||||
async function signInWithHanzo() {
|
||||
await signIn("hanzo-iam", { callbackUrl: "/onboarding" });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center bg-gradient-to-br from-indigo-50 via-white to-cyan-100">
|
||||
<div className="grid w-full max-w-md grid-cols-1 gap-5 rounded-xl border bg-white p-10 shadow">
|
||||
<AuthFormHeader page="signin" />
|
||||
<>
|
||||
{isHanzoIAMEnabled && (
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
type="button"
|
||||
onClick={signInWithHanzo}
|
||||
className="bg-red-500 hover:bg-red-600 text-white"
|
||||
>
|
||||
<svg className="mr-2 h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
|
||||
</svg>
|
||||
Sign in with <span className="font-bold">Hanzo</span>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
loading={isPasskeyLoading}
|
||||
|
||||
@@ -3,3 +3,5 @@ import { env } from "@/env";
|
||||
export const IS_GOOGLE_AUTH_ENABLED = !!(
|
||||
env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET
|
||||
);
|
||||
|
||||
export const IS_HANZO_IAM_ENABLED = !!process.env.HANZO_IAM_CLIENT_ID;
|
||||
|
||||
@@ -18,11 +18,43 @@ import {
|
||||
import type { MemberStatusEnum } from "@/prisma/enums";
|
||||
import { type TPrismaOrTransaction, db } from "@/server/db";
|
||||
import { verifyAuthenticationResponse } from "@simplewebauthn/server";
|
||||
import type { OAuthConfig } from "next-auth/providers/oauth";
|
||||
import CredentialsProvider from "next-auth/providers/credentials";
|
||||
import GoogleProvider from "next-auth/providers/google";
|
||||
import { cache } from "react";
|
||||
import { getUserByEmail, getUserById } from "./user";
|
||||
|
||||
const HANZO_IAM_URL = process.env.HANZO_IAM_URL;
|
||||
const HANZO_IAM_CLIENT_ID = process.env.HANZO_IAM_CLIENT_ID;
|
||||
const HANZO_IAM_CLIENT_SECRET = process.env.HANZO_IAM_CLIENT_SECRET;
|
||||
|
||||
function HanzoIAMProvider(): OAuthConfig<any> {
|
||||
const issuer = HANZO_IAM_URL || "https://hanzo.id";
|
||||
return {
|
||||
id: "hanzo-iam",
|
||||
name: process.env.HANZO_IAM_PROVIDER_NAME || "Hanzo",
|
||||
type: "oauth",
|
||||
wellKnown: `${issuer}/.well-known/openid-configuration`,
|
||||
clientId: HANZO_IAM_CLIENT_ID || "",
|
||||
clientSecret: HANZO_IAM_CLIENT_SECRET || "",
|
||||
authorization: { params: { scope: "openid profile email" } },
|
||||
idToken: false,
|
||||
userinfo: { url: `${issuer}/oauth/userinfo` },
|
||||
profile(profile) {
|
||||
return {
|
||||
id: profile.sub,
|
||||
name:
|
||||
profile.displayName ||
|
||||
profile.name ||
|
||||
profile.preferred_username,
|
||||
email: profile.email,
|
||||
image: profile.avatar || profile.picture,
|
||||
};
|
||||
},
|
||||
allowDangerousEmailAccountLinking: true,
|
||||
};
|
||||
}
|
||||
|
||||
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
|
||||
const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET;
|
||||
export const JWT_SECRET = new TextEncoder().encode(env.NEXTAUTH_SECRET);
|
||||
@@ -151,6 +183,7 @@ export const authOptions: NextAuthOptions = {
|
||||
strategy: "jwt",
|
||||
},
|
||||
providers: [
|
||||
...(HANZO_IAM_CLIENT_ID ? [HanzoIAMProvider()] : []),
|
||||
CredentialsProvider({
|
||||
name: "Credentials",
|
||||
credentials: {
|
||||
|
||||
Reference in New Issue
Block a user