auth flow

This commit is contained in:
alma 2025-05-02 13:05:56 +02:00
parent ce09b18a6f
commit 1443d8d9b2
3 changed files with 97 additions and 14 deletions

View File

@ -1,7 +1,7 @@
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
import { getServerSession } from 'next-auth/next'; import { getServerSession } from 'next-auth/next';
import { authOptions } from '@/app/api/auth/[...nextauth]/route'; import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { ResponsiveIframe } from '@/app/components/responsive-iframe'; import ResponsiveIframe from '@/app/components/responsive-iframe';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
// Use environment variables for real service URLs // Use environment variables for real service URLs
@ -62,7 +62,7 @@ export default async function SectionPage(props: { params: { section: string } }
<ResponsiveIframe <ResponsiveIframe
src={proxyUrl} src={proxyUrl}
className="w-full h-full border-none" className="w-full h-full border-none"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen={true}
/> />
</div> </div>
) )

View File

@ -1,6 +1,57 @@
import NextAuth, { NextAuthOptions } from "next-auth"; import NextAuth, { NextAuthOptions } from "next-auth";
import KeycloakProvider from "next-auth/providers/keycloak"; import KeycloakProvider from "next-auth/providers/keycloak";
// Define Keycloak profile type
interface KeycloakProfile {
sub: string;
email?: string;
name?: string;
preferred_username?: string;
given_name?: string;
family_name?: string;
realm_access?: {
roles: string[];
};
}
// Define custom profile type
interface CustomProfile {
id: string;
name?: string | null;
email?: string | null;
username: string;
first_name: string;
last_name: string;
role: string[];
}
// Declare module augmentation for NextAuth types
declare module "next-auth" {
interface Session {
user: {
id: string;
name?: string | null;
email?: string | null;
image?: string | null;
username: string;
first_name: string;
last_name: string;
role: string[];
};
accessToken?: string;
}
interface JWT {
sub?: string;
accessToken?: string;
refreshToken?: string;
role?: string[];
username?: string;
first_name?: string;
last_name?: string;
}
}
// Simple, minimal implementation - NO REFRESH TOKEN LOGIC // Simple, minimal implementation - NO REFRESH TOKEN LOGIC
export const authOptions: NextAuthOptions = { export const authOptions: NextAuthOptions = {
providers: [ providers: [
@ -8,37 +59,69 @@ export const authOptions: NextAuthOptions = {
clientId: process.env.KEYCLOAK_CLIENT_ID || "", clientId: process.env.KEYCLOAK_CLIENT_ID || "",
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET || "", clientSecret: process.env.KEYCLOAK_CLIENT_SECRET || "",
issuer: process.env.KEYCLOAK_ISSUER || "", issuer: process.env.KEYCLOAK_ISSUER || "",
profile(profile: any) {
// Extract roles from the profile
const roles = profile.realm_access?.roles || [];
return {
id: profile.sub,
name: profile.name || profile.preferred_username,
email: profile.email,
image: null,
role: roles.map((role: string) => role.replace(/^ROLE_/, '').toLowerCase()),
first_name: profile.given_name || '',
last_name: profile.family_name || '',
username: profile.preferred_username || profile.email?.split('@')[0] || '',
};
}
}), }),
], ],
session: { session: {
strategy: "jwt", strategy: "jwt",
maxAge: 8 * 60 * 60, // 8 hours only maxAge: 8 * 60 * 60, // 8 hours
}, },
callbacks: { callbacks: {
// Simple JWT callback - no refresh logic async jwt({ token, account, profile }: any) {
async jwt({ token, account }) { if (account && profile) {
if (account) { // Just store access token and critical fields
// Initial sign-in, store tokens
token.accessToken = account.access_token; token.accessToken = account.access_token;
token.sub = account.providerAccountId; // Make sure roles are available
if (profile.role) {
token.role = profile.role;
token.username = profile.username || '';
token.first_name = profile.first_name || '';
token.last_name = profile.last_name || '';
}
} }
return token; return token;
}, },
// Simple session callback async session({ session, token }: any) {
async session({ session, token }) { // Pass necessary info to the session
session.accessToken = token.accessToken; session.accessToken = token.accessToken;
if (session.user) { if (session.user) {
session.user.id = token.sub || ""; session.user.id = token.sub || "";
// Ensure roles are passed to the session
if (token.role) {
session.user.role = token.role;
session.user.username = token.username || '';
session.user.first_name = token.first_name || '';
session.user.last_name = token.last_name || '';
} else {
// Fallback roles
session.user.role = ["user"];
session.user.username = '';
session.user.first_name = '';
session.user.last_name = '';
}
} }
return session; return session;
} }
}, },
// Redirect to signin page for any errors
pages: { pages: {
signIn: '/signin', signIn: '/signin',
error: '/signin', error: '/signin',
}, },
// Set reasonable cookie options
cookies: { cookies: {
sessionToken: { sessionToken: {
name: 'next-auth.session-token', name: 'next-auth.session-token',

View File

@ -1,7 +1,7 @@
import { getServerSession } from "next-auth/next"; import { getServerSession } from "next-auth/next";
import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { authOptions } from "@/app/api/auth/[...nextauth]/route";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { ResponsiveIframe } from "@/app/components/responsive-iframe"; import ResponsiveIframe from "@/app/components/responsive-iframe";
export default async function Page() { export default async function Page() {
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);
@ -15,12 +15,12 @@ export default async function Page() {
<div className="w-full h-full px-4 pt-12 pb-4"> <div className="w-full h-full px-4 pt-12 pb-4">
<ResponsiveIframe <ResponsiveIframe
src={process.env.NEXT_PUBLIC_IFRAME_DRIVE_URL || ''} src={process.env.NEXT_PUBLIC_IFRAME_DRIVE_URL || ''}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
className="relative" className="relative"
style={{ style={{
marginTop: '-50px', // Adjust this value based on the Nextcloud navbar height marginTop: '-50px', // Adjust this value based on the Nextcloud navbar height
height: 'calc(100% + 50px)' // Compensate for the negative margin height: 'calc(100% + 50px)' // Compensate for the negative margin
}} }}
allowFullScreen={true}
/> />
</div> </div>
</main> </main>