Skip to content

Commit 49c9cce

Browse files
committed
Webpack served UI functional until kratos self service login
Signed-off-by: Dom Del Nano <ddelnano@gmail.com>
1 parent 3527665 commit 49c9cce

4 files changed

Lines changed: 202 additions & 107 deletions

File tree

k8s/cloud/base/ory_auth/hydra/hydra_config.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,33 @@ data:
99
serve:
1010
cookies:
1111
same_site_mode: Strict
12+
public:
13+
cors:
14+
enabled: true
15+
allowed_origins:
16+
- https://*.${PL_DOMAIN_NAME}
17+
allowed_methods:
18+
- POST
19+
- GET
20+
- PUT
21+
- PATCH
22+
- DELETE
23+
allowed_headers:
24+
- Authorization
25+
- Content-Type
26+
exposed_headers:
27+
- Content-Type
1228
1329
oidc:
1430
subject_identifiers:
1531
supported_types:
1632
- pairwise
1733
- public
34+
dynamic_client_registration:
35+
enabled: true
36+
default_scope:
37+
- openid
38+
- offline
39+
- notifications
40+
- gist
41+
- vizier

k8s/cloud/base/ory_auth/hydra/hydra_deployment.yaml

Lines changed: 5 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,12 @@ spec:
122122
value: /certs/server.crt
123123
- name: SERVE_TLS_KEY_PATH
124124
value: /certs/server.key
125+
- name: SERVE_PUBLIC_CORS_ALLOWED_ORIGINS_0
126+
value: https://$(PL_DOMAIN_NAME)
127+
- name: SERVE_PUBLIC_CORS_ALLOWED_ORIGINS_1
128+
value: https://work.$(PL_DOMAIN_NAME)
125129
- name: PL_WORK_DOMAIN
126-
value: work.$(PL_DOMAIN_NAME)
130+
value: work.$(PL_DOMAIN_NAME):8080
127131
- name: PL_OAUTH_DOMAIN
128132
value: $(PL_WORK_DOMAIN)/oauth
129133
- name: HYDRA_URL
@@ -161,74 +165,6 @@ spec:
161165
runAsUser: 10100
162166
seccompProfile:
163167
type: RuntimeDefault
164-
- name: client-create-or-update
165-
imagePullPolicy: IfNotPresent
166-
image: docker.io/ddelnano/hydra:v2.3.0-alpine-go1.23@sha256:371daf5cc7477ae607d1011440da784c366b74c7cc8acea2322826b82c789fe1
167-
command: ['sh', '-c', 'set -x;
168-
echo "Waiting for hydra to be ready";
169-
URL="${PL_HYDRA_ADMIN_HOST}/health/ready";
170-
until [
171-
$(wget --no-check-certificate --spider --quiet --server-response ${URL} 2>&1 |
172-
awk ''NR==1{print $2}'') -eq 200
173-
]; do
174-
echo "waiting for ${URL}";
175-
sleep 2;
176-
done;
177-
178-
count=$(hydra list oauth2-clients -e ${PL_HYDRA_ADMIN_HOST} --skip-tls-verify --format json | jq ".items | length");
179-
success=$?;
180-
if [ $success -ne 0 ]; then
181-
echo "Error checking for existing client";
182-
exit 1;
183-
fi;
184-
185-
if [ $count -gt 0 ]; then
186-
echo "Client already created";
187-
sleep infinity;
188-
fi;
189-
190-
echo "Creating OAuth2 client";
191-
hydra create oauth2-client
192-
--endpoint ${PL_HYDRA_ADMIN_HOST}
193-
--secret "${HYDRA_CLIENT_SECRET}"
194-
--format json
195-
--skip-tls-verify
196-
--grant-type authorization_code
197-
--grant-type refresh_token
198-
--grant-type implicit
199-
--response-type code
200-
--response-type id_token
201-
--response-type token
202-
--scope openid
203-
--scope offline
204-
--scope notifications
205-
--scope gist
206-
--scope vizier
207-
--redirect-uri "https://${PL_DOMAIN_NAME}/oauth/auth/callback"
208-
--redirect-uri "https://work.${PL_DOMAIN_NAME}/auth/callback"
209-
--name "pixie-auth-client";
210-
sleep infinity;
211-
']
212-
envFrom:
213-
- configMapRef:
214-
name: pl-domain-config
215-
- configMapRef:
216-
name: pl-ory-service-config
217-
env:
218-
- name: HYDRA_CLIENT_SECRET
219-
valueFrom:
220-
secretKeyRef:
221-
name: pl-hydra-secrets
222-
key: CLIENT_SECRET
223-
securityContext:
224-
allowPrivilegeEscalation: false
225-
capabilities:
226-
drop:
227-
- ALL
228-
runAsNonRoot: true
229-
runAsUser: 10100
230-
seccompProfile:
231-
type: RuntimeDefault
232168
restartPolicy: Always
233169
volumes:
234170
- name: config

k8s/cloud/base/ory_auth/kratos/kratos_deployment.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ spec:
100100
- name: LOG_LEVEL
101101
value: trace
102102
- name: PL_WORK_DOMAIN
103-
value: work.$(PL_DOMAIN_NAME)
103+
value: work.$(PL_DOMAIN_NAME):8080
104104
- name: PL_OAUTH_DOMAIN
105105
value: $(PL_WORK_DOMAIN)/oauth
106106
- name: FRONTEND_URL

src/ui/src/pages/auth/hydra-oauth-provider.ts

Lines changed: 172 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ import * as QueryString from 'query-string';
2828
import { FormField, FormStructure } from 'app/components';
2929
import { HydraInvitationForm } from 'app/containers/admin/hydra-invitation-form';
3030
import { HydraButtons, RejectHydraSignup } from 'app/containers/auth/hydra-buttons';
31-
import { AUTH_CLIENT_ID, AUTH_URI } from 'app/containers/constants';
31+
import { AUTH_CLIENT_ID, AUTH_URI, OAUTH_PROVIDER } from 'app/containers/constants';
3232

3333
import { CallbackArgs, getSignupArgs, getLoginArgs } from './callback-url';
3434

3535
export const PasswordError = new Error('Kratos identity server error: Password method not found in flows.');
3636
export const FlowIDError = new Error('Auth server requires a flow parameter in the query string, but none were found.');
3737

38-
const kratosClient = V0alpha2ApiFactory(null, '/oauth/kratos');
38+
const kratosUri = `${location.protocol}//${location.host}/oauth/kratos`;
39+
const kratosClient = V0alpha2ApiFactory(null, kratosUri);
3940

4041
// Renders a form with an error and no fields.
4142
const displayErrorFormStructure = (error: Error): FormStructure => ({
@@ -81,56 +82,190 @@ function nodesToFormFields(nodes: Array<UiNode>): Array<FormField> {
8182
.filter(node => node);
8283
}
8384

85+
// Dynamic client registration types
86+
interface OAuth2Client {
87+
client_id: string;
88+
client_secret?: string;
89+
client_name?: string;
90+
redirect_uris?: string[];
91+
grant_types?: string[];
92+
response_types?: string[];
93+
scope?: string;
94+
token_endpoint_auth_method?: string;
95+
}
96+
97+
interface DynamicClientRegistrationResponse extends OAuth2Client {
98+
client_id_issued_at?: number;
99+
client_secret_expires_at?: number;
100+
registration_client_uri?: string;
101+
registration_access_token?: string;
102+
}
103+
104+
// Cache for the dynamically registered client ID
105+
let cachedClientId: string | null = null;
106+
107+
async function getDynamicClientId(): Promise<string> {
108+
// Only use dynamic registration for hydra provider
109+
if (OAUTH_PROVIDER !== 'hydra') {
110+
return AUTH_CLIENT_ID;
111+
}
112+
113+
// Return cached client ID if available
114+
if (cachedClientId) {
115+
return cachedClientId;
116+
}
117+
118+
// Use /oauth/hydra prefix which will be stripped by the proxy
119+
// Preserve the current port (e.g., :8080 for dev server)
120+
const registrationEndpoint = `${window.location.protocol}//${window.location.host}/oauth/hydra/oauth2/register`;
121+
console.log(`Dynamic client registration endpoint: ${registrationEndpoint}`);
122+
123+
const redirect_uri = `${window.location.protocol}//${window.location.host}/auth/callback`;
124+
console.log(`Using redirect URI: ${redirect_uri}`);
125+
126+
const clientMetadata: Partial<OAuth2Client> = {
127+
client_name: 'pixie-auth-client',
128+
redirect_uris: [
129+
redirect_uri,
130+
],
131+
grant_types: ['authorization_code', 'refresh_token', 'implicit'],
132+
response_types: ['code', 'id_token', 'token'],
133+
scope: 'openid offline notifications gist vizier',
134+
token_endpoint_auth_method: 'none',
135+
};
136+
137+
try {
138+
const response = await fetch(registrationEndpoint, {
139+
method: 'POST',
140+
headers: {
141+
'Content-Type': 'application/json',
142+
'Accept': 'application/json',
143+
},
144+
body: JSON.stringify(clientMetadata),
145+
});
146+
147+
if (!response.ok) {
148+
const errorText = await response.text();
149+
throw new Error(`Dynamic client registration failed: ${response.status} ${response.statusText} - ${errorText}`);
150+
}
151+
152+
const data: DynamicClientRegistrationResponse = await response.json();
153+
154+
if (!data.client_id) {
155+
throw new Error('No client_id returned from dynamic registration');
156+
}
157+
158+
// Cache the client ID for future use
159+
cachedClientId = data.client_id;
160+
161+
console.log('Successfully registered dynamic OAuth2 client:', data.client_id);
162+
return data.client_id;
163+
} catch (error) {
164+
console.error('Failed to register dynamic client:', error);
165+
// Fall back to static client ID if dynamic registration fails
166+
console.warn('Falling back to static client ID');
167+
return AUTH_CLIENT_ID;
168+
}
169+
}
170+
84171
export const HydraClient = {
85-
userManager: new UserManager({
86-
authority: AUTH_URI,
87-
client_id: AUTH_CLIENT_ID,
88-
redirect_uri: `${window.location.origin}/auth/callback`,
89-
loadUserInfo: false,
90-
scope: 'vizier',
91-
response_type: 'token',
92-
}),
172+
userManager: null as UserManager | null,
173+
userManagerPromise: null as Promise<UserManager> | null,
174+
175+
async getUserManager(): Promise<UserManager> {
176+
// Return existing manager if available
177+
if (this.userManager) {
178+
return this.userManager;
179+
}
180+
181+
// Return existing promise if initialization is in progress
182+
if (this.userManagerPromise) {
183+
return this.userManagerPromise;
184+
}
185+
186+
// Start initialization
187+
this.userManagerPromise = (async () => {
188+
const clientId = await getDynamicClientId();
189+
190+
this.userManager = new UserManager({
191+
authority: AUTH_URI,
192+
client_id: clientId,
193+
redirect_uri: `${window.location.origin}/auth/callback`,
194+
loadUserInfo: false,
195+
scope: 'vizier',
196+
response_type: 'token',
197+
});
198+
199+
return this.userManager;
200+
})();
201+
202+
return this.userManagerPromise;
203+
},
93204

94205
loginRequest(): void {
95-
this.userManager.signinRedirect({
96-
prompt: 'login',
97-
state: {
98-
redirectArgs: getLoginArgs(),
99-
},
206+
// Fire and forget - the redirect will happen once the client ID is obtained
207+
this.getUserManager().then(userManager => {
208+
userManager.signinRedirect({
209+
prompt: 'login',
210+
state: {
211+
redirectArgs: getLoginArgs(),
212+
},
213+
});
214+
}).catch(error => {
215+
console.error('Failed to initiate login:', error);
216+
// Could potentially show an error to the user here
100217
});
101218
},
102219

103220
signupRequest(): void {
104-
this.userManager.signinRedirect({
105-
prompt: 'login',
106-
state: {
107-
redirectArgs: getSignupArgs(),
108-
},
221+
// Fire and forget - the redirect will happen once the client ID is obtained
222+
this.getUserManager().then(userManager => {
223+
userManager.signinRedirect({
224+
prompt: 'login',
225+
state: {
226+
redirectArgs: getSignupArgs(),
227+
},
228+
});
229+
}).catch(error => {
230+
console.error('Failed to initiate signup:', error);
231+
// Could potentially show an error to the user here
109232
});
110233
},
111234

112235
refetchToken(): void {
113-
this.userManager.signinRedirect({
114-
state: {
115-
redirectArgs: getLoginArgs(),
116-
},
236+
// Fire and forget - the redirect will happen once the client ID is obtained
237+
this.getUserManager().then(userManager => {
238+
userManager.signinRedirect({
239+
state: {
240+
redirectArgs: getLoginArgs(),
241+
},
242+
});
243+
}).catch(error => {
244+
console.error('Failed to refetch token:', error);
245+
// Could potentially show an error to the user here
117246
});
118247
},
119248

120249
handleToken(): Promise<CallbackArgs> {
121-
return new Promise<CallbackArgs>((resolve, reject) => {
122-
this.userManager.signinRedirectCallback()
123-
.then((user) => {
124-
if (!user) {
125-
reject(new Error('user is undefined, please try logging in again'));
126-
}
127-
resolve({
128-
redirectArgs: user.state.redirectArgs,
129-
token: {
130-
accessToken: user.access_token,
131-
},
132-
});
133-
}).catch(reject);
250+
return new Promise<CallbackArgs>(async (resolve, reject) => {
251+
try {
252+
const userManager = await this.getUserManager();
253+
const user = await userManager.signinRedirectCallback();
254+
255+
if (!user) {
256+
reject(new Error('user is undefined, please try logging in again'));
257+
return;
258+
}
259+
260+
resolve({
261+
redirectArgs: user.state.redirectArgs,
262+
token: {
263+
accessToken: user.access_token,
264+
},
265+
});
266+
} catch (error) {
267+
reject(error);
268+
}
134269
});
135270
},
136271

0 commit comments

Comments
 (0)