Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/llm-wiki/08-auth-and-terms-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Platform UI auth and JupyterHub auth are related but separate:
8. Hub sets its own session cookie (SameSite=Lax, Secure by default). Any valid Keycloak user for the Hub client is currently allowed (`allow_all = True`).
9. Before spawn, Hub injects the Keycloak access token into the notebook pod as `MIP_TOKEN`, refreshing via stored refresh tokens when needed.
10. Running notebooks can refresh `MIP_TOKEN` through Hub `/notebook/hub/api/platform-token` via the mip client transport.
11. Remote use (account page, notebook flag only): the user creates a personal Hub API token at `/notebook/hub/token` (a top-level navigation that nginx proxies to Hub, not back into the shell) sets `MIP_URL` and runs `mip login` (which saves the token to a file) for the mip client on their own computer. The UI only links to Hub's page; it never requests, stores or shows the token.
11. Remote use (account page): shown when `GET /services/auth-config` answers (the backend has a public Keycloak client, `KEYCLOAK_CLI_CLIENT_ID`). "Create access token" runs an authorization-code sign-in with PKCE as that client (`scope=openid offline_access`, redirect back to `/account`, code exchanged by the browser, `cli-token.ts`) and shows the offline refresh token once; nothing stores it. Users paste it into `mip login --token` or `MIP_API_TOKEN`; the mip client renews access with Keycloak itself. The page also explains `mip login` (device flow). JupyterHub is not involved.

Key files: `src/app/pages/notebook/hub-session.ts`, `hub-http.ts`, `jupyterhub-api.ts`, `nginx.conf.template`.

Expand Down
119 changes: 90 additions & 29 deletions src/app/pages/account-page/account-page.component.css
Original file line number Diff line number Diff line change
Expand Up @@ -489,25 +489,14 @@
}

/* ===== Use MIP from your own computer ===== */
.remote-steps {
margin: 0;
padding-left: 20px;
.remote-access {
font-size: 14px;
color: #111827;
line-height: 1.5;
color: #111827;
}

.remote-steps li + li {
margin-top: 14px;
}

.remote-steps p {
margin: 8px 0 0;
color: #6b7280;
}

.remote-steps pre {
margin: 8px 0 0;
.remote-access pre {
margin: 0;
padding: 10px 12px;
border-radius: 10px;
background: #f9fafb;
Expand All @@ -517,25 +506,64 @@
overflow-x: auto;
}

.remote-steps code {
.remote-access code {
font-family: var(--font-mono);
}

.token-btn {
display: inline-flex;
align-items: center;
gap: 8px;
background-color: var(--primary-color);
color: #ffffff;
padding: 8px 14px;
border-radius: var(--radius-md);
font-size: 14px;
font-weight: 500;
text-decoration: none;
/* Inline code only; blocks are styled by pre above. */
.remote-access :not(pre) > code {
padding: 1px 5px;
border-radius: 4px;
background: #f3f4f6;
font-size: 0.92em;
}

.token-btn:hover {
background-color: var(--primary-dark);
.remote-text {
margin: 14px 0 8px;
}

.remote-note {
margin: 6px 0 0;
font-size: 13px;
color: #6b7280;
}

.account-link {
color: var(--primary-color);
}

.remote-options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
gap: 12px 16px;
}

.remote-option {
padding: 14px 16px;
border-radius: 10px;
background: #f9fafb;
border: 1px solid #e5e7eb;
min-width: 0;
}

.remote-option pre {
margin-top: 10px;
background: #ffffff;
}

.remote-option :not(pre) > code {
background: #e5e7eb;
}

.remote-option .remote-text {
margin: 10px 0 0;
}

.remote-access h4 {
margin: 0 0 10px;
font-size: 14px;
font-weight: 600;
color: #111827;
}

.snippet {
Expand Down Expand Up @@ -573,3 +601,36 @@
font-size: 12px;
color: #6b7280;
}

.create-token-btn {
display: inline-flex;
align-items: center;
gap: 8px;
background-color: var(--primary-color);
color: #ffffff;
border: none;
padding: 8px 14px;
border-radius: var(--radius-md);
font-size: 14px;
font-weight: 500;
cursor: pointer;
}

.create-token-btn:hover:not(:disabled) {
background-color: var(--primary-dark);
}

.create-token-btn:disabled {
opacity: 0.6;
cursor: default;
}

.token-value code {
white-space: pre-wrap;
word-break: break-all;
}

.token-error {
margin: 8px 0 0;
color: #991b1b;
}
95 changes: 65 additions & 30 deletions src/app/pages/account-page/account-page.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,41 +44,76 @@ <h3>Profile Information</h3>
</div>
</section>

@if (notebookEnabled) {
@if (authConfig()) {
<section class="info-section remote-access">
<h3>Use MIP from your own computer</h3>
<ol class="remote-steps">
<li>
<a class="token-btn" [href]="hubTokenUrl" target="_blank" rel="noopener"
aria-label="Create API token (opens in a new tab)">

<p class="remote-text">Install the client and point it at this portal:</p>
<div class="snippet">
<pre><code>{{ setupSnippet }}</code></pre>
<button class="copy-btn snippet-copy-btn" type="button" (click)="copy(setupSnippet, copyStatus)"
aria-label="Copy setup commands">
<i class="fas fa-copy"></i>
<span>Copy</span>
</button>
</div>
<span class="copy-status" aria-live="polite">{{ copyStatus() }}</span>
<p class="remote-note">In Windows PowerShell, write <code>$env:MIP_URL = "…"</code> instead of <code>export</code>.</p>

<p class="remote-text">Then sign in, one of two ways:</p>
<div class="remote-options">
<div class="remote-option">
<h4>No browser on that machine</h4>
<button class="create-token-btn" type="button" (click)="createToken()" [disabled]="creatingToken()">
<i class="fas fa-key"></i>
<span>Create API token</span>
</a>
<p>
In <strong>Permissions</strong>, enter <code>custom:mip-platform-token</code> and choose an expiry.
The token is shown once. You can revoke it on the same page.
<span>{{ creatingToken() ? 'Creating…' : 'Create access token' }}</span>
</button>
<p class="remote-note">You confirm with your MIP sign-in. The token is shown only once.</p>
@if (tokenError()) {
<p class="token-error" role="alert">{{ tokenError() }}</p>
}
@if (cliToken()) {
<div class="snippet">
<pre class="token-value"><code>{{ cliToken() }}</code></pre>
<button class="copy-btn token-copy-btn" type="button" (click)="copy(cliToken(), tokenCopyStatus)"
aria-label="Copy access token">
<i class="fas fa-copy"></i>
<span>Copy</span>
</button>
</div>
<span class="copy-status" aria-live="polite">{{ tokenCopyStatus() }}</span>
<p class="remote-note">
Treat it like a password: it gives your full MIP data access for up to 6 months, until
it goes unused for 30 days, or until you run <code>mip logout</code>.
</p>
}
<p class="remote-text">
On that machine, run <code>mip login --token</code> and paste it. Scripts can set
<code>MIP_API_TOKEN</code> instead, but every program started from there, AI assistants
included, can read it.
</p>
</li>
<li>
In a terminal on your computer, run:
<div class="snippet">
<pre><code>{{ setupSnippet }}</code></pre>
<button class="copy-btn" type="button" (click)="copySetupSnippet()"
aria-label="Copy setup commands">
<i class="fas fa-copy"></i>
<span>Copy</span>
</button>
</div>
<span class="copy-status" aria-live="polite">{{ copyStatus() }}</span>
<p>
<code>mip login</code> asks for your token without showing it.
In Windows PowerShell, write <code>$env:MIP_URL = "…"</code> instead of
<code>export MIP_URL=…</code>.
</div>

<div class="remote-option">
<h4>A browser on any device</h4>
<p class="remote-text">
Run <code>mip login</code>. It shows a link and a code: open the link, sign in, check
that the page shows the same code, and approve.
</p>
<p>Then start Python in the same terminal and run:</p>
<pre><code>import mip; client = mip.Client.from_env()</code></pre>
</li>
</ol>
<p class="remote-note">Never approve a code that someone else sent you.</p>
</div>
</div>

<p class="remote-text">Then, in Python:</p>
<pre><code>import mip
client = mip.Client.from_env()</code></pre>
<p class="remote-note">
Each sign-in lasts at most 6 months, and ends after 30 days without use. To end it sooner,
run <code>mip logout</code> on that computer. Without that computer, remove
"MIP command line" under Applications in your
<a class="account-link" [href]="authConfig()!.issuer + '/account/applications'" target="_blank"
rel="noopener">sign-in account</a>: that ends every computer's sign-in.
</p>
</section>
}
</section>
Expand Down
Loading
Loading