feat(cli): Login via Frontend (#956)

* CLI login page

* Add Success Page

* Change redirect on decline

* Center & Size Loading Windmill Icon

* Fix goto debug

* fix spinner

* Workspace from URL

* Handle login --token & --workspace

Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
Kai Jellinghaus
2022-11-29 16:01:53 +01:00
committed by GitHub
co-authored by Ruben Fiszel
parent b2324fa51d
commit 2c31a9cbdf
3 changed files with 115 additions and 29 deletions
+58 -29
View File
@@ -1,44 +1,74 @@
import { Command } from "https://deno.land/x/cliffy@v0.25.4/command/mod.ts";
import {
Input,
Secret,
} from "https://deno.land/x/cliffy@v0.25.4/prompt/mod.ts";
import { GlobalOptions } from "./types.ts";
import {
setClient,
UserService,
} from "https://deno.land/x/windmill@v1.50.0/mod.ts";
import { colors } from "https://deno.land/x/cliffy@v0.25.4/ansi/colors.ts";
import { getStore } from "./store.ts";
import { getDefaultRemote, getRemote } from "./remote.ts";
import { getDefaultRemote } from "./remote.ts";
import { getAvailablePort } from "https://deno.land/x/port@1.0.0/mod.ts";
export type Options = GlobalOptions;
async function login(
{ remote, baseUrl }: Options,
email?: string,
password?: string
) {
if (remote) {
baseUrl = baseUrl ?? (await getRemote(remote))?.baseUrl;
}
async function login({
baseUrl,
token: existingToken,
workspace: existingWorkspace,
}: Options) {
baseUrl = baseUrl ?? (await getDefaultRemote())?.baseUrl;
baseUrl = baseUrl ?? "https://app.windmill.dev";
setClient("no-token", baseUrl);
const urlStore = await getStore(baseUrl);
email = email ?? (await Input.prompt({ message: "Input your Email" }));
password =
password ?? (await Secret.prompt({ message: "Input your Password" }));
const token = await UserService.login({
requestBody: {
email: email,
password: password,
},
});
if (existingToken) {
await Deno.writeTextFile(urlStore + "token", existingToken);
if (existingWorkspace) {
await Deno.writeTextFile(
urlStore + "default_workspace_id",
existingWorkspace!
);
console.log(
colors.bold.underline.green(
"Successfully logged in & switched to workspace " + existingWorkspace
)
);
} else {
console.log(colors.bold.underline.green("Successfully logged in"));
}
return;
}
// Start listening on port 8080 of localhost.
const port = await getAvailablePort();
if (port == undefined) {
console.log(colors.red.underline("failed to aquire port"));
return;
}
const server = Deno.listen({ transport: "tcp", port });
console.log(`Login by going to ${baseUrl}/user/cli?port=${port}`);
const firstConnection = await server.accept();
const httpFirstConnection = Deno.serveHttp(firstConnection);
const firstRequest = (await httpFirstConnection.nextRequest())!;
const params = new URL(firstRequest.request.url!).searchParams;
const token = params.get("token");
const workspace = params.get("workspace");
await firstRequest?.respondWith(
Response.redirect(baseUrl + "/user/cli-success", 302)
);
if (token === undefined || token === null) {
console.log(colors.red.underline("Invalid Request. Failed to log in."));
return;
}
await Deno.writeTextFile(urlStore + "token", token);
console.log(colors.bold.underline.green("Successfully logged in!"));
await Deno.writeTextFile(urlStore + "default_workspace_id", workspace!);
console.log(
colors.bold.underline.green(
"Successfully logged in & switched to workspace " + workspace
)
);
httpFirstConnection.close();
server.close();
}
export async function getToken(baseUrl: string): Promise<string> {
@@ -59,7 +89,6 @@ const command = new Command()
.description(
"Log into windmill. The credentials are not stored, but the token they are exchanged for will be."
)
.arguments("[email:string] [password:string]")
.action(login as any);
export default command;
@@ -0,0 +1,18 @@
<script lang="ts">
import { goto } from '$app/navigation'
import CenteredModal from '$lib/components/CenteredModal.svelte'
import WindmillIcon from '$lib/components/icons/WindmillIcon.svelte'
import { sendUserToast } from '$lib/utils'
sendUserToast('Sucess. You can return to your terminal now.')
goto('/')
</script>
<CenteredModal title="Redirecting to Home...">
<div class="w-full ">
<div class="block m-auto w-20">
<WindmillIcon class="animate-[spin_6s_linear_infinite]" height="80px" width="80px" />
</div>
</div>
</CenteredModal>
+39
View File
@@ -0,0 +1,39 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { UserService } from '$lib/gen'
import { page } from '$app/stores'
import CenteredModal from '$lib/components/CenteredModal.svelte'
import { Button } from '$lib/components/common'
import { workspaceStore, usersWorkspaceStore } from '$lib/stores'
let port = Number($page.url.searchParams.get('port'))
port = port == 0 || port == NaN ? 80 : port
async function authorizeToken(): Promise<void> {
const newToken = await UserService.createToken({
requestBody: {
label: 'External Tool Token',
expiration: undefined
}
})
await goto('http://localhost:' + port + '?token=' + newToken + '&workspace=' + $workspaceStore)
}
</script>
<CenteredModal title="Authorize this request for a Token">
<p class="text-center text-lg">
Token will be posted to your local machine to port {port}
</p>
<div class="flex flex-row justify-between pt-4 gap-x-1">
<Button variant="border" size="sm" href="/">Decline</Button>
<button
class="place-items-end bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 border rounded"
type="button"
on:click={authorizeToken}
>
Authorize
</button>
</div>
</CenteredModal>