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;
|
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) => {
|
app.get("/api/services/:serviceSlug/launch", requireSession, (req, res) => {
|
||||||
|
|
@ -449,7 +449,7 @@ app.get("/api/services/:serviceSlug/launch", requireSession, (req, res) => {
|
||||||
return;
|
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) => {
|
app.post("/api/internal/handoff/consume", (req, res) => {
|
||||||
|
|
@ -486,6 +486,7 @@ app.post("/api/internal/handoff/consume", (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
ok: true,
|
ok: true,
|
||||||
serviceSlug,
|
serviceSlug,
|
||||||
|
launcherSessionId: handoff.launcherSessionId ?? null,
|
||||||
user: {
|
user: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
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) => {
|
app.get("/api/profile", requireSession, (req, res) => {
|
||||||
const { actor, data } = getLauncherProfileContext(req.nodedcSession);
|
const { actor, data } = getLauncherProfileContext(req.nodedcSession);
|
||||||
const user = findLauncherUser(data, actor.id);
|
const user = findLauncherUser(data, actor.id);
|
||||||
|
|
@ -1111,7 +1175,7 @@ app.get("/tasker-workspace-invite/:taskerInviteRequestId", (req, res) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handoffToken = createServiceHandoff("task-manager", runtimeContext.user);
|
const handoffToken = createServiceHandoff("task-manager", runtimeContext.user, { launcherSessionId: session.id });
|
||||||
const taskBaseUrl = getTaskBaseUrl();
|
const taskBaseUrl = getTaskBaseUrl();
|
||||||
const targetUrl = new URL("/auth/nodedc/handoff/", taskBaseUrl);
|
const targetUrl = new URL("/auth/nodedc/handoff/", taskBaseUrl);
|
||||||
targetUrl.searchParams.set("token", handoffToken);
|
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)) {
|
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 targetUrl = new URL("/auth/nodedc/handoff/", getEngineLaunchTargetUrl(app));
|
||||||
const nextPath = sanitizeReturnTo(returnTo);
|
const nextPath = sanitizeReturnTo(returnTo);
|
||||||
|
|
||||||
|
|
@ -3164,7 +3228,7 @@ function redirectToServiceLaunchTarget(res, serviceSlug, runtimeContext, app, re
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isBimViewerServiceSlug(serviceSlug)) {
|
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 targetUrl = new URL("/auth/nodedc/handoff", getBimViewerBaseUrl(app));
|
||||||
const nextPath = sanitizeReturnTo(returnTo);
|
const nextPath = sanitizeReturnTo(returnTo);
|
||||||
|
|
||||||
|
|
@ -3183,7 +3247,7 @@ function redirectToServiceLaunchTarget(res, serviceSlug, runtimeContext, app, re
|
||||||
return;
|
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 targetUrl = new URL("/auth/nodedc/handoff/", getTaskBaseUrl());
|
||||||
const nextPath = sanitizeReturnTo(returnTo);
|
const nextPath = sanitizeReturnTo(returnTo);
|
||||||
|
|
||||||
|
|
@ -3626,12 +3690,13 @@ function resolveTaskManagerWorkspaceAssignments(data, user) {
|
||||||
return [...bySlug.values()];
|
return [...bySlug.values()];
|
||||||
}
|
}
|
||||||
|
|
||||||
function createServiceHandoff(serviceSlug, user) {
|
function createServiceHandoff(serviceSlug, user, { launcherSessionId = null } = {}) {
|
||||||
pruneExpiredServiceHandoffs();
|
pruneExpiredServiceHandoffs();
|
||||||
|
|
||||||
const token = randomBase64Url(48);
|
const token = randomBase64Url(48);
|
||||||
serviceHandoffs.set(token, {
|
serviceHandoffs.set(token, {
|
||||||
serviceSlug,
|
serviceSlug,
|
||||||
|
launcherSessionId: typeof launcherSessionId === "string" && launcherSessionId ? launcherSessionId : null,
|
||||||
user: {
|
user: {
|
||||||
sub: user.sub,
|
sub: user.sub,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue