mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: Login via CLI (#298)
Fixes #210 - this isPR implements `coder login` in the case where the default user is already created. This change adds: - A prompt in the case where there is not an initial user that opens the server URL + requests a session token - This ports over some code from v1 for the `openURL` and `isWSL` functions to support opening the browser - A `/api/v2/api-keys` endpoint that can be `POST`'d to in order to request a new api key for a user - This route was inspired by the v1 functionality - A `cli-auth` route + page that shows the generated api key - Tests for the new code + storybook for the new UI The `/cli-auth` route, like in v1, is very minimal: <img width="624" alt="Screen Shot 2022-02-16 at 5 05 07 PM" src="https://user-images.githubusercontent.com/88213859/154384627-78ab9841-27bf-490f-9bbe-23f8173c9e97.png"> And the terminal UX looks like this: 
This commit is contained in:
+13
@@ -139,3 +139,16 @@ export const logout = async (): Promise<void> => {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
export const getApiKey = async (): Promise<{ key: string }> => {
|
||||
const response = await fetch("/api/v2/users/me/keys", {
|
||||
method: "POST",
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const body = await response.json()
|
||||
throw new Error(body.message)
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Story } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { CliAuthToken, CliAuthTokenProps } from "./CliAuthToken"
|
||||
|
||||
export default {
|
||||
title: "SignIn/CliAuthToken",
|
||||
component: CliAuthToken,
|
||||
argTypes: {
|
||||
sessionToken: { control: "text", defaultValue: "some-session-token" },
|
||||
},
|
||||
}
|
||||
|
||||
const Template: Story<CliAuthTokenProps> = (args) => <CliAuthToken {...args} />
|
||||
|
||||
export const Example = Template.bind({})
|
||||
Example.args = {}
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react"
|
||||
import { screen } from "@testing-library/react"
|
||||
import { render } from "../../test_helpers"
|
||||
|
||||
import { CliAuthToken } from "./CliAuthToken"
|
||||
|
||||
describe("CliAuthToken", () => {
|
||||
it("renders content", async () => {
|
||||
// When
|
||||
render(<CliAuthToken sessionToken="test-token" />)
|
||||
|
||||
// Then
|
||||
await screen.findByText("Session Token")
|
||||
await screen.findByText("test-token")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import Paper from "@material-ui/core/Paper"
|
||||
import Typography from "@material-ui/core/Typography"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import React from "react"
|
||||
import { CodeExample } from "../CodeExample"
|
||||
|
||||
export interface CliAuthTokenProps {
|
||||
sessionToken: string
|
||||
}
|
||||
|
||||
export const CliAuthToken: React.FC<CliAuthTokenProps> = ({ sessionToken }) => {
|
||||
const styles = useStyles()
|
||||
return (
|
||||
<Paper className={styles.container}>
|
||||
<Typography className={styles.title}>Session Token</Typography>
|
||||
<CodeExample code={sessionToken} />
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
title: {
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
container: {
|
||||
maxWidth: "680px",
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
}))
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./CliAuthToken"
|
||||
export * from "./SignInForm"
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { getApiKey } from "../api"
|
||||
import { CliAuthToken } from "../components/SignIn"
|
||||
|
||||
import { FullScreenLoader } from "../components/Loader/FullScreenLoader"
|
||||
import { useUser } from "../contexts/UserContext"
|
||||
|
||||
const CliAuthenticationPage: React.FC = () => {
|
||||
const { me } = useUser(true)
|
||||
const styles = useStyles()
|
||||
|
||||
const [apiKey, setApiKey] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (me?.id) {
|
||||
void getApiKey().then(({ key }) => {
|
||||
setApiKey(key)
|
||||
})
|
||||
}
|
||||
}, [me?.id])
|
||||
|
||||
if (!apiKey) {
|
||||
return <FullScreenLoader />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<CliAuthToken sessionToken={apiKey} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
root: {
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
}))
|
||||
|
||||
export default CliAuthenticationPage
|
||||
Reference in New Issue
Block a user