@@ -21,41 +21,74 @@ import axios from "axios"
2121import { apiClient } from "./client"
2222import { navigate } from "@/lib/navigation"
2323import { REALM_HEADER_NAME } from "@/lib/constants"
24- import type { OAuthTokenResponse } from "@/types/api"
24+ import type { OAuthTokenResponse , AuthType } from "@/types/api"
2525
2626// Always use relative URL to go through the proxy (dev server or production server)
2727// This avoids CORS issues by proxying requests through the server
2828// The server.ts proxy handles /api routes in production, and Vite handles them in development
29- const TOKEN_URL = "/api/catalog/v1/oauth/tokens"
29+ const INTERNAL_TOKEN_URL = "/api/catalog/v1/oauth/tokens"
3030
3131// Log OAuth URL in development only
3232if ( import . meta. env . DEV ) {
33- console . log ( "🔐 Using OAuth token URL:" , TOKEN_URL )
33+ console . log ( "🔐 Using Internal OAuth token URL:" , INTERNAL_TOKEN_URL )
3434}
3535
3636export const authApi = {
3737 getToken : async (
3838 clientId : string ,
3939 clientSecret : string ,
40- realm ?: string
40+ authType : AuthType ,
41+ realm ?: string ,
42+ polarisRealm ?: string
4143 ) : Promise < OAuthTokenResponse > => {
4244 const formData = new URLSearchParams ( )
43- formData . append ( "grant_type" , "client_credentials" )
4445 formData . append ( "client_id" , clientId )
4546 formData . append ( "client_secret" , clientSecret )
46- formData . append ( "scope" , "PRINCIPAL_ROLE:ALL" )
47+
48+ // Internal auth uses scope, external (Keycloak) uses grant_type
49+ if ( authType === "internal" ) {
50+ formData . append ( "scope" , "PRINCIPAL_ROLE:ALL" )
51+ formData . append ( "grant_type" , "client_credentials" )
52+ } else {
53+ formData . append ( "grant_type" , "client_credentials" )
54+ }
4755
4856 const headers : Record < string , string > = {
4957 "Content-Type" : "application/x-www-form-urlencoded" ,
5058 }
5159
52- // Add realm header if provided
53- if ( realm ) {
54- headers [ REALM_HEADER_NAME ] = realm
60+ let tokenUrl : string
61+
62+ if ( authType === "keycloak" ) {
63+ // For Keycloak, use relative path that goes through proxy (dev server or production server)
64+ // This avoids CORS issues by proxying requests through the server
65+ // The vite.config.ts proxy handles /keycloak routes in development
66+ // In production, a similar proxy should be configured on the server
67+ if ( ! realm ) {
68+ throw new Error ( "Keycloak realm is required for Keycloak authentication" )
69+ }
70+ // Use relative path that goes through proxy
71+ tokenUrl = `/keycloak/realms/${ realm } /protocol/openid-connect/token`
72+ // Add Polaris realm header if provided (for Polaris API calls)
73+ if ( polarisRealm ) {
74+ headers [ REALM_HEADER_NAME ] = polarisRealm
75+ }
76+ } else {
77+ // For internal, use the relative URL that goes through proxy
78+ tokenUrl = INTERNAL_TOKEN_URL
79+ // Add realm header if provided (for internal auth)
80+ if ( realm ) {
81+ headers [ REALM_HEADER_NAME ] = realm
82+ }
83+ }
84+
85+ // Log token URL in development only
86+ if ( import . meta. env . DEV ) {
87+ console . log ( "🔐 Using token URL:" , tokenUrl , "Auth type:" , authType )
5588 }
5689
5790 const response = await axios . post < OAuthTokenResponse > (
58- TOKEN_URL ,
91+ tokenUrl ,
5992 formData ,
6093 {
6194 headers,
@@ -73,13 +106,14 @@ export const authApi = {
73106 subjectToken : string ,
74107 subjectTokenType : string
75108 ) : Promise < OAuthTokenResponse > => {
109+ // Token exchange always uses internal endpoint
76110 const formData = new URLSearchParams ( )
77111 formData . append ( "grant_type" , "urn:ietf:params:oauth:grant-type:token-exchange" )
78112 formData . append ( "subject_token" , subjectToken )
79113 formData . append ( "subject_token_type" , subjectTokenType )
80114
81115 const response = await axios . post < OAuthTokenResponse > (
82- TOKEN_URL ,
116+ INTERNAL_TOKEN_URL ,
83117 formData ,
84118 {
85119 headers : {
@@ -97,13 +131,14 @@ export const authApi = {
97131 } ,
98132
99133 refreshToken : async ( accessToken : string ) : Promise < OAuthTokenResponse > => {
134+ // Token refresh always uses internal endpoint
100135 const formData = new URLSearchParams ( )
101136 formData . append ( "grant_type" , "urn:ietf:params:oauth:grant-type:token-exchange" )
102137 formData . append ( "subject_token" , accessToken )
103138 formData . append ( "subject_token_type" , "urn:ietf:params:oauth:token-type:access_token" )
104139
105140 const response = await axios . post < OAuthTokenResponse > (
106- TOKEN_URL ,
141+ INTERNAL_TOKEN_URL ,
107142 formData ,
108143 {
109144 headers : {
@@ -121,6 +156,9 @@ export const authApi = {
121156
122157 logout : ( ) : void => {
123158 apiClient . clearAccessToken ( )
159+ localStorage . removeItem ( "polaris_realm" )
160+ localStorage . removeItem ( "polaris_auth_type" )
161+ localStorage . removeItem ( "polaris_keycloak_realm" )
124162 // Use a small delay to allow toast to show before redirect
125163 setTimeout ( ( ) => {
126164 navigate ( "/login" , true )
0 commit comments