carnet api
This commit is contained in:
parent
2765730b47
commit
ae6de407d7
@ -35,6 +35,7 @@ declare module "next-auth" {
|
|||||||
role: string[];
|
role: string[];
|
||||||
};
|
};
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
nextcloudToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface JWT {
|
interface JWT {
|
||||||
@ -92,6 +93,29 @@ async function refreshAccessToken(token: JWT) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add NextCloud token exchange logic
|
||||||
|
async function exchangeKeycloakTokenForNextCloud(token: string): Promise<string> {
|
||||||
|
const response = await fetch(`${process.env.NEXTCLOUD_URL}/apps/oauth2/api/v1/token`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: new URLSearchParams({
|
||||||
|
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
|
||||||
|
subject_token: token,
|
||||||
|
subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
|
||||||
|
requested_token_type: 'urn:ietf:params:oauth:token-type:access_token'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to exchange token with NextCloud');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.access_token;
|
||||||
|
}
|
||||||
|
|
||||||
export const authOptions: NextAuthOptions = {
|
export const authOptions: NextAuthOptions = {
|
||||||
providers: [
|
providers: [
|
||||||
KeycloakProvider({
|
KeycloakProvider({
|
||||||
@ -139,107 +163,31 @@ export const authOptions: NextAuthOptions = {
|
|||||||
maxAge: 30 * 24 * 60 * 60, // 30 days
|
maxAge: 30 * 24 * 60 * 60, // 30 days
|
||||||
},
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
async jwt({ token, account, profile }) {
|
async jwt({ token, account }) {
|
||||||
console.log('JWT callback start:', {
|
if (account?.access_token) {
|
||||||
hasAccount: !!account,
|
token.accessToken = account.access_token;
|
||||||
hasProfile: !!profile,
|
// Only set refresh token if it exists
|
||||||
token
|
if (account.refresh_token) {
|
||||||
});
|
token.refreshToken = account.refresh_token;
|
||||||
|
|
||||||
if (account && profile) {
|
|
||||||
const keycloakProfile = profile as KeycloakProfile;
|
|
||||||
console.log('JWT callback profile:', {
|
|
||||||
rawRoles: keycloakProfile.roles,
|
|
||||||
realmAccess: keycloakProfile.realm_access,
|
|
||||||
profile: keycloakProfile
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get roles from realm_access
|
|
||||||
const roles = keycloakProfile.realm_access?.roles || [];
|
|
||||||
console.log('JWT callback raw roles:', roles);
|
|
||||||
|
|
||||||
// Clean up roles by removing ROLE_ prefix and converting to lowercase
|
|
||||||
const cleanRoles = roles.map((role: string) =>
|
|
||||||
role.replace(/^ROLE_/, '').toLowerCase()
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log('JWT callback cleaned roles:', cleanRoles);
|
|
||||||
|
|
||||||
token.accessToken = account.access_token ?? '';
|
|
||||||
token.refreshToken = account.refresh_token ?? '';
|
|
||||||
token.accessTokenExpires = account.expires_at ?? 0;
|
|
||||||
token.sub = keycloakProfile.sub;
|
|
||||||
token.role = cleanRoles;
|
|
||||||
token.username = keycloakProfile.preferred_username ?? '';
|
|
||||||
token.first_name = keycloakProfile.given_name ?? '';
|
|
||||||
token.last_name = keycloakProfile.family_name ?? '';
|
|
||||||
|
|
||||||
console.log('JWT callback final token:', {
|
|
||||||
tokenRoles: token.role,
|
|
||||||
token
|
|
||||||
});
|
|
||||||
} else if (token.accessToken) {
|
|
||||||
// Decode the token to get roles
|
|
||||||
try {
|
|
||||||
const decoded = jwtDecode<DecodedToken>(token.accessToken);
|
|
||||||
console.log('Decoded token:', decoded);
|
|
||||||
|
|
||||||
if (decoded.realm_access?.roles) {
|
|
||||||
const roles = decoded.realm_access.roles;
|
|
||||||
console.log('Decoded token roles:', roles);
|
|
||||||
|
|
||||||
// Clean up roles by removing ROLE_ prefix and converting to lowercase
|
|
||||||
const cleanRoles = roles.map((role: string) =>
|
|
||||||
role.replace(/^ROLE_/, '').toLowerCase()
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log('Decoded token cleaned roles:', cleanRoles);
|
|
||||||
token.role = cleanRoles;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error decoding token:', error);
|
|
||||||
}
|
}
|
||||||
|
// Set expiry if it exists, otherwise set a default
|
||||||
|
token.accessTokenExpires = account.expires_at ? account.expires_at * 1000 : Date.now() + 3600 * 1000;
|
||||||
}
|
}
|
||||||
|
return token;
|
||||||
if (Date.now() < (token.accessTokenExpires as number) * 1000) {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
return refreshAccessToken(token);
|
|
||||||
},
|
},
|
||||||
async session({ session, token }) {
|
async session({ session, token }) {
|
||||||
if (token.error) {
|
if (token) {
|
||||||
throw new Error(token.error);
|
session.accessToken = token.accessToken;
|
||||||
|
// We'll handle Nextcloud authentication separately using app passwords
|
||||||
|
session.user = {
|
||||||
|
...session.user,
|
||||||
|
id: token.sub || '',
|
||||||
|
role: token.role || [],
|
||||||
|
username: token.username || '',
|
||||||
|
first_name: token.first_name || '',
|
||||||
|
last_name: token.last_name || '',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Session callback token:', {
|
|
||||||
tokenRoles: token.role,
|
|
||||||
tokenSub: token.sub,
|
|
||||||
tokenUsername: token.username,
|
|
||||||
token
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ensure we have an array of roles
|
|
||||||
const userRoles = Array.isArray(token.role) ? token.role : [];
|
|
||||||
console.log('Session callback userRoles:', userRoles);
|
|
||||||
|
|
||||||
session.user = {
|
|
||||||
id: token.sub ?? '',
|
|
||||||
email: token.email ?? null,
|
|
||||||
name: token.name ?? null,
|
|
||||||
image: null,
|
|
||||||
username: token.username ?? '',
|
|
||||||
first_name: token.first_name ?? '',
|
|
||||||
last_name: token.last_name ?? '',
|
|
||||||
role: userRoles,
|
|
||||||
};
|
|
||||||
session.accessToken = token.accessToken;
|
|
||||||
|
|
||||||
console.log('Session callback final session:', {
|
|
||||||
userRoles: session.user.role,
|
|
||||||
session
|
|
||||||
});
|
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -100,6 +100,7 @@ export default function CarnetPage() {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
title: note.title,
|
||||||
content: note.content,
|
content: note.content,
|
||||||
date: note.lastEdited.toISOString(),
|
date: note.lastEdited.toISOString(),
|
||||||
category: 'Notes'
|
category: 'Notes'
|
||||||
|
|||||||
@ -9,7 +9,7 @@ interface EditorProps {
|
|||||||
title: string;
|
title: string;
|
||||||
content: string;
|
content: string;
|
||||||
lastEdited: Date;
|
lastEdited: Date;
|
||||||
};
|
} | null;
|
||||||
onSave?: (note: { id: string; title: string; content: string; lastEdited: Date }) => void;
|
onSave?: (note: { id: string; title: string; content: string; lastEdited: Date }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,9 +33,9 @@ export const Editor: React.FC<EditorProps> = ({ note, onSave }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (note?.id) {
|
if (onSave) {
|
||||||
onSave?.({
|
onSave({
|
||||||
id: note.id,
|
id: note?.id || Date.now().toString(),
|
||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
lastEdited: new Date()
|
lastEdited: new Date()
|
||||||
|
|||||||
@ -5,32 +5,52 @@ export class NextCloudService {
|
|||||||
private webdav: WebDAV;
|
private webdav: WebDAV;
|
||||||
private basePath: string = '/Personal/Carnet';
|
private basePath: string = '/Personal/Carnet';
|
||||||
private subFolders: string[] = ['Journal', 'Santé', 'Notes'];
|
private subFolders: string[] = ['Journal', 'Santé', 'Notes'];
|
||||||
|
private token: string;
|
||||||
|
private appPassword?: string;
|
||||||
|
private baseUrl: string;
|
||||||
|
|
||||||
constructor(token: string) {
|
constructor(token: string, appPassword?: string) {
|
||||||
const nextcloudUrl = process.env.NEXTCLOUD_URL;
|
this.token = token;
|
||||||
|
this.appPassword = appPassword;
|
||||||
|
this.baseUrl = process.env.NEXTCLOUD_URL || '';
|
||||||
console.log('NextCloud Configuration:', {
|
console.log('NextCloud Configuration:', {
|
||||||
baseUrl: nextcloudUrl,
|
baseUrl: this.baseUrl,
|
||||||
tokenLength: token?.length || 0,
|
tokenLength: token?.length || 0,
|
||||||
hasToken: !!token
|
hasToken: !!token
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!nextcloudUrl) {
|
if (!this.baseUrl) {
|
||||||
throw new Error('NEXTCLOUD_URL environment variable is not set');
|
throw new Error('NEXTCLOUD_URL environment variable is not set');
|
||||||
}
|
}
|
||||||
|
|
||||||
const webdavUrl = `${nextcloudUrl}/remote.php/dav/files`;
|
const webdavUrl = `${this.baseUrl}/remote.php/dav/files`;
|
||||||
console.log('WebDAV endpoint:', webdavUrl);
|
console.log('WebDAV endpoint:', webdavUrl);
|
||||||
|
|
||||||
this.webdav = new WebDAV(
|
this.webdav = new WebDAV(
|
||||||
webdavUrl,
|
webdavUrl,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: this.getHeaders(),
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isUsingAppPassword(): boolean {
|
||||||
|
return !!this.appPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getHeaders(): HeadersInit {
|
||||||
|
if (this.isUsingAppPassword()) {
|
||||||
|
return {
|
||||||
|
'Authorization': `Basic ${Buffer.from(`${this.token}:${this.appPassword}`).toString('base64')}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'Authorization': `Bearer ${this.token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async testConnection() {
|
private async testConnection() {
|
||||||
try {
|
try {
|
||||||
console.log('Testing WebDAV connection...');
|
console.log('Testing WebDAV connection...');
|
||||||
|
|||||||
38
types/next-auth.d.ts
vendored
38
types/next-auth.d.ts
vendored
@ -7,26 +7,23 @@ declare module "next-auth" {
|
|||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
username: string;
|
username: string;
|
||||||
email: string;
|
email?: string | null;
|
||||||
role: string[];
|
role: string[];
|
||||||
} & DefaultSession["user"];
|
} & DefaultSession["user"];
|
||||||
accessToken?: string;
|
accessToken: string;
|
||||||
refreshToken?: string;
|
refreshToken?: string;
|
||||||
rocketChatToken?: string | null;
|
|
||||||
rocketChatUserId?: string | null;
|
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface JWT {
|
interface JWT {
|
||||||
accessToken?: string;
|
sub?: string;
|
||||||
|
accessToken: string;
|
||||||
refreshToken?: string;
|
refreshToken?: string;
|
||||||
accessTokenExpires?: number;
|
accessTokenExpires: number;
|
||||||
first_name?: string;
|
first_name: string;
|
||||||
last_name?: string;
|
last_name: string;
|
||||||
username?: string;
|
username: string;
|
||||||
role?: string[];
|
role: string[];
|
||||||
rocketChatToken?: string | null;
|
|
||||||
rocketChatUserId?: string | null;
|
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +32,7 @@ declare module "next-auth" {
|
|||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
username: string;
|
username: string;
|
||||||
email: string;
|
email?: string | null;
|
||||||
role: string[];
|
role: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,15 +50,14 @@ declare module "next-auth" {
|
|||||||
|
|
||||||
declare module "next-auth/jwt" {
|
declare module "next-auth/jwt" {
|
||||||
interface JWT {
|
interface JWT {
|
||||||
accessToken?: string;
|
sub?: string;
|
||||||
|
accessToken: string;
|
||||||
refreshToken?: string;
|
refreshToken?: string;
|
||||||
accessTokenExpires?: number;
|
accessTokenExpires: number;
|
||||||
first_name?: string;
|
first_name: string;
|
||||||
last_name?: string;
|
last_name: string;
|
||||||
username?: string;
|
username: string;
|
||||||
role?: string[];
|
role: string[];
|
||||||
rocketChatToken?: string;
|
|
||||||
rocketChatUserId?: string;
|
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user