Expose Launcher session validation for service handoff
This commit is contained in:
parent
34ffa8ca4d
commit
71c62579dc
|
|
@ -436,7 +436,7 @@ app.get("/api/services/:serviceSlug/optional-launch", (req, res) => {
|
|||
return;
|
||||
}
|
||||
|
||||
redirectToServiceLaunchTarget(res, serviceSlug, runtimeContext, app, req.query.next_path || req.query.returnTo || "/");
|
||||
redirectToServiceLaunchTarget(res, serviceSlug, runtimeContext, app, req.query.next_path || req.query.returnTo || "/", session);
|
||||
});
|
||||
|
||||
app.get("/api/services/:serviceSlug/launch", requireSession, (req, res) => {
|
||||
|
|
@ -449,7 +449,7 @@ app.get("/api/services/:serviceSlug/launch", requireSession, (req, res) => {
|
|||
return;
|
||||
}
|
||||
|
||||
redirectToServiceLaunchTarget(res, serviceSlug, runtimeContext, app, req.query.next_path || req.query.returnTo || "/");
|
||||
redirectToServiceLaunchTarget(res, serviceSlug, runtimeContext, app, req.query.next_path || req.query.returnTo || "/", req.nodedcSession);
|
||||
});
|
||||
|
||||
app.post("/api/internal/handoff/consume", (req, res) => {
|
||||
|
|
@ -486,6 +486,7 @@ app.post("/api/internal/handoff/consume", (req, res) => {
|
|||
res.json({
|
||||
ok: true,
|
||||
serviceSlug,
|
||||
launcherSessionId: handoff.launcherSessionId ?? null,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
|
|
@ -498,6 +499,69 @@ app.post("/api/internal/handoff/consume", (req, res) => {
|
|||
});
|
||||
});
|
||||
|
||||
app.post("/api/internal/session/validate", (req, res) => {
|
||||
if (!isInternalRequestAuthorized(req)) {
|
||||
res.status(config.internalAccessToken ? 401 : 503).json({
|
||||
ok: false,
|
||||
active: false,
|
||||
error: config.internalAccessToken ? "internal_session_unauthorized" : "internal_session_not_configured",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const serviceSlug = sanitizeServiceSlug(req.body?.serviceSlug);
|
||||
const launcherSessionId = typeof req.body?.launcherSessionId === "string" ? req.body.launcherSessionId : "";
|
||||
|
||||
if (!launcherSessionId) {
|
||||
res.json({ ok: true, active: false, error: "launcher_session_missing" });
|
||||
return;
|
||||
}
|
||||
|
||||
const session = sessions.get(launcherSessionId);
|
||||
if (!session || session.expiresAt < Date.now()) {
|
||||
if (session) sessions.delete(session.id);
|
||||
res.json({ ok: true, active: false, error: "launcher_session_not_found" });
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionAccess = getSessionAccessState(session);
|
||||
if (!sessionAccess.ok) {
|
||||
sessions.delete(session.id);
|
||||
res.json({ ok: true, active: false, error: sessionAccess.error || "launcher_session_inactive" });
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshot = controlPlaneStore.getSnapshot({ name: "NODE.DC session validation" });
|
||||
const runtimeContext = sessionAccess.runtimeContext;
|
||||
const user = findInternalAccessUser(snapshot.data, {
|
||||
subject: runtimeContext.user.sub,
|
||||
email: runtimeContext.user.email,
|
||||
});
|
||||
const groups = user ? resolveRequiredGroups(snapshot.data, user) : runtimeContext.groups;
|
||||
const app = getAppsForUser(groups).find((candidate) => candidate.slug === serviceSlug);
|
||||
|
||||
if (!user || !app?.hasAccess || app.status !== "active") {
|
||||
res.json({ ok: true, active: false, error: "service_access_denied" });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({
|
||||
ok: true,
|
||||
active: true,
|
||||
serviceSlug,
|
||||
launcherSessionId,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
avatarUrl: resolveUserAvatarPublicUrl(user),
|
||||
subject: user.authentikUserId || runtimeContext.user.sub,
|
||||
authentikUserId: user.authentikUserId ?? null,
|
||||
groups,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/api/profile", requireSession, (req, res) => {
|
||||
const { actor, data } = getLauncherProfileContext(req.nodedcSession);
|
||||
const user = findLauncherUser(data, actor.id);
|
||||
|
|
@ -1111,7 +1175,7 @@ app.get("/tasker-workspace-invite/:taskerInviteRequestId", (req, res) => {
|
|||
return;
|
||||
}
|
||||
|
||||
const handoffToken = createServiceHandoff("task-manager", runtimeContext.user);
|
||||
const handoffToken = createServiceHandoff("task-manager", runtimeContext.user, { launcherSessionId: session.id });
|
||||
const taskBaseUrl = getTaskBaseUrl();
|
||||
const targetUrl = new URL("/auth/nodedc/handoff/", taskBaseUrl);
|
||||
targetUrl.searchParams.set("token", handoffToken);
|
||||
|
|
@ -3147,9 +3211,9 @@ function buildOptionalServiceFallbackUrl(serviceSlug, app, fallbackPath) {
|
|||
}
|
||||
}
|
||||
|
||||
function redirectToServiceLaunchTarget(res, serviceSlug, runtimeContext, app, returnTo = "/") {
|
||||
function redirectToServiceLaunchTarget(res, serviceSlug, runtimeContext, app, returnTo = "/", session = null) {
|
||||
if (isEngineServiceSlug(serviceSlug)) {
|
||||
const handoffToken = createServiceHandoff("nodedc", runtimeContext.user);
|
||||
const handoffToken = createServiceHandoff("nodedc", runtimeContext.user, { launcherSessionId: session?.id });
|
||||
const targetUrl = new URL("/auth/nodedc/handoff/", getEngineLaunchTargetUrl(app));
|
||||
const nextPath = sanitizeReturnTo(returnTo);
|
||||
|
||||
|
|
@ -3164,7 +3228,7 @@ function redirectToServiceLaunchTarget(res, serviceSlug, runtimeContext, app, re
|
|||
}
|
||||
|
||||
if (isBimViewerServiceSlug(serviceSlug)) {
|
||||
const handoffToken = createServiceHandoff(serviceSlug, runtimeContext.user);
|
||||
const handoffToken = createServiceHandoff(serviceSlug, runtimeContext.user, { launcherSessionId: session?.id });
|
||||
const targetUrl = new URL("/auth/nodedc/handoff", getBimViewerBaseUrl(app));
|
||||
const nextPath = sanitizeReturnTo(returnTo);
|
||||
|
||||
|
|
@ -3183,7 +3247,7 @@ function redirectToServiceLaunchTarget(res, serviceSlug, runtimeContext, app, re
|
|||
return;
|
||||
}
|
||||
|
||||
const handoffToken = createServiceHandoff(serviceSlug, runtimeContext.user);
|
||||
const handoffToken = createServiceHandoff(serviceSlug, runtimeContext.user, { launcherSessionId: session?.id });
|
||||
const targetUrl = new URL("/auth/nodedc/handoff/", getTaskBaseUrl());
|
||||
const nextPath = sanitizeReturnTo(returnTo);
|
||||
|
||||
|
|
@ -3626,12 +3690,13 @@ function resolveTaskManagerWorkspaceAssignments(data, user) {
|
|||
return [...bySlug.values()];
|
||||
}
|
||||
|
||||
function createServiceHandoff(serviceSlug, user) {
|
||||
function createServiceHandoff(serviceSlug, user, { launcherSessionId = null } = {}) {
|
||||
pruneExpiredServiceHandoffs();
|
||||
|
||||
const token = randomBase64Url(48);
|
||||
serviceHandoffs.set(token, {
|
||||
serviceSlug,
|
||||
launcherSessionId: typeof launcherSessionId === "string" && launcherSessionId ? launcherSessionId : null,
|
||||
user: {
|
||||
sub: user.sub,
|
||||
email: user.email,
|
||||
|
|
|
|||
Loading…
Reference in New Issue