mirror of https://github.com/coder/coder.git
Compare commits
7 Commits
b194101214
...
b0792ea751
Author | SHA1 | Date |
---|---|---|
elasticspoon | b0792ea751 | |
Colin Adler | 13dd526f11 | |
recanman | b20c63c185 | |
Michael Brewer | 060f023174 | |
elasticspoon | 341feddf1b | |
elasticspoon | f0616ea5e5 | |
elasticspoon | f45143bfc7 |
80
cli/login.go
80
cli/login.go
|
@ -274,7 +274,52 @@ func (r *RootCmd) login() *serpent.Command {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Check for session token from flags or environment.
|
||||
sessionToken, _ := inv.ParsedFlags().GetString(varToken)
|
||||
if sessionToken != "" && !useTokenForSession {
|
||||
// If a session token is provided on the cli, use it to generate
|
||||
// a new one. This is because the cli `--token` flag provides
|
||||
// a token for the command being invoked. We should not store
|
||||
// this token, and `/logout` should not delete it.
|
||||
// /login should generate a new token and store that.
|
||||
client.SetSessionToken(sessionToken)
|
||||
// Use CreateAPIKey over CreateToken because this is a session
|
||||
// key that should not show on the `tokens` page. This should
|
||||
// match the same behavior of the `/cli-auth` page for generating
|
||||
// a session token.
|
||||
key, err := client.CreateAPIKey(ctx, codersdk.Me)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create api key: %w", err)
|
||||
}
|
||||
sessionToken = key.Key
|
||||
}
|
||||
|
||||
// Check for existing session token on disk, and validate user data.
|
||||
// If the token exists but is invalid, then it is probably expired.
|
||||
// Skip this check if the user has provided a valid token; a new token
|
||||
// should be generated.
|
||||
var userResp codersdk.User
|
||||
if configToken, _ := r.createConfig().Session().Read(); sessionToken == "" && configToken != "" {
|
||||
client.SetSessionToken(configToken)
|
||||
userResp, err = client.User(ctx, codersdk.Me)
|
||||
if err == nil {
|
||||
_, err = cliui.Prompt(inv, cliui.PromptOptions{
|
||||
Text: fmt.Sprintf("You are already authenticated %s. Are you sure you want to log in again?",
|
||||
pretty.Sprint(cliui.DefaultStyles.Keyword, userResp.Username)),
|
||||
IsConfirm: true,
|
||||
Default: cliui.ConfirmYes,
|
||||
})
|
||||
if errors.Is(err, cliui.Canceled) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
sessionToken = ""
|
||||
}
|
||||
|
||||
// If we still don't have a session token, prompt the user for one.
|
||||
if sessionToken == "" {
|
||||
authURL := *serverURL
|
||||
// Don't use filepath.Join, we don't want to use the os separator
|
||||
|
@ -287,7 +332,8 @@ func (r *RootCmd) login() *serpent.Command {
|
|||
}
|
||||
|
||||
sessionToken, err = cliui.Prompt(inv, cliui.PromptOptions{
|
||||
Text: "Paste your token here:",
|
||||
Text: "Paste your token here:",
|
||||
Secret: true,
|
||||
Validate: func(token string) error {
|
||||
client.SetSessionToken(token)
|
||||
_, err := client.User(ctx, codersdk.Me)
|
||||
|
@ -300,29 +346,16 @@ func (r *RootCmd) login() *serpent.Command {
|
|||
if err != nil {
|
||||
return xerrors.Errorf("paste token prompt: %w", err)
|
||||
}
|
||||
} else if !useTokenForSession {
|
||||
// If a session token is provided on the cli, use it to generate
|
||||
// a new one. This is because the cli `--token` flag provides
|
||||
// a token for the command being invoked. We should not store
|
||||
// this token, and `/logout` should not delete it.
|
||||
// /login should generate a new token and store that.
|
||||
client.SetSessionToken(sessionToken)
|
||||
// Use CreateAPIKey over CreateToken because this is a session
|
||||
// key that should not show on the `tokens` page. This should
|
||||
// match the same behavior of the `/cli-auth` page for generating
|
||||
// a session token.
|
||||
key, err := client.CreateAPIKey(ctx, "me")
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create api key: %w", err)
|
||||
}
|
||||
sessionToken = key.Key
|
||||
}
|
||||
|
||||
// If we didn't login via session token on disk
|
||||
// Login to get user data - verify it is OK before persisting
|
||||
client.SetSessionToken(sessionToken)
|
||||
resp, err := client.User(ctx, codersdk.Me)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get user: %w", err)
|
||||
if userResp.Email == "" {
|
||||
client.SetSessionToken(sessionToken)
|
||||
userResp, err = client.User(ctx, codersdk.Me)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get user: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
config := r.createConfig()
|
||||
|
@ -335,7 +368,8 @@ func (r *RootCmd) login() *serpent.Command {
|
|||
return xerrors.Errorf("write server url: %w", err)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(inv.Stdout, Caret+"Welcome to Coder, %s! You're authenticated.\n", pretty.Sprint(cliui.DefaultStyles.Keyword, resp.Username))
|
||||
_, _ = fmt.Fprintf(inv.Stdout, Caret+"Welcome to Coder, %s! You're authenticated.\n",
|
||||
pretty.Sprint(cliui.DefaultStyles.Keyword, userResp.Username))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -366,7 +400,7 @@ func (r *RootCmd) login() *serpent.Command {
|
|||
},
|
||||
{
|
||||
Flag: "use-token-as-session",
|
||||
Description: "By default, the CLI will generate a new session token when logging in. This flag will instead use the provided token as the session token.",
|
||||
Description: "By default, the CLI will generate a new session token when logging in. This flag will instead use the provided token as the session token. See `coder --help` for more information on how to set a token.",
|
||||
Value: serpent.BoolOf(&useTokenForSession),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -225,6 +225,8 @@ func TestLogin(t *testing.T) {
|
|||
|
||||
inv, root := clitest.New(t, "login", "--no-open")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
err := root.Session().Delete()
|
||||
assert.NoError(t, err)
|
||||
|
||||
doneChan := make(chan struct{})
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
|
@ -291,6 +293,110 @@ func TestLogin(t *testing.T) {
|
|||
<-doneChan
|
||||
})
|
||||
|
||||
t.Run("ExistingUserExpiredSessionToken", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
inv, root := clitest.New(t, "login", "--no-open", client.URL.String())
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
|
||||
err := root.Session().Write("an-expired-token")
|
||||
assert.NoError(t, err)
|
||||
|
||||
doneChan := make(chan struct{})
|
||||
go func() {
|
||||
defer close(doneChan)
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
pty.ExpectMatch("Paste your token here:")
|
||||
pty.WriteLine(client.SessionToken())
|
||||
pty.ExpectMatch("Welcome to Coder")
|
||||
<-doneChan
|
||||
})
|
||||
|
||||
t.Run("ExistingUserRelogin", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
inv, root := clitest.New(t, "login", "--no-open")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
|
||||
doneChan := make(chan struct{})
|
||||
go func() {
|
||||
defer close(doneChan)
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
pty.ExpectMatch("You are already authenticated")
|
||||
pty.ExpectMatch("Are you sure you want to log in again?")
|
||||
pty.WriteLine("yes")
|
||||
pty.ExpectMatch("Paste your token here:")
|
||||
pty.WriteLine(client.SessionToken())
|
||||
pty.ExpectMatch("Welcome to Coder")
|
||||
<-doneChan
|
||||
})
|
||||
|
||||
t.Run("ExistingUserReloginRejection", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
inv, root := clitest.New(t, "login", "--no-open")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
doneChan := make(chan struct{})
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
go func() {
|
||||
defer close(doneChan)
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
pty.ExpectMatch("You are already authenticated")
|
||||
pty.ExpectMatch("Are you sure you want to log in again?")
|
||||
pty.WriteLine("no")
|
||||
<-doneChan
|
||||
})
|
||||
|
||||
t.Run("AuthenticatedUserTokenFlagValid", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
inv, root := clitest.New(t, "login", client.URL.String(), "--no-open", "--token", client.SessionToken())
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
|
||||
sessionFile, err := root.Session().Read()
|
||||
require.NoError(t, err)
|
||||
// This **should not be equal** to the token we passed in.
|
||||
require.NotEqual(t, client.SessionToken(), sessionFile)
|
||||
})
|
||||
|
||||
t.Run("AuthenticatedUserTokenFlagValidUseTokenAsSession", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
inv, root := clitest.New(t, "login", client.URL.String(), "--no-open", "--token", client.SessionToken(), "--use-token-as-session")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
|
||||
sessionFile, err := root.Session().Read()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, client.SessionToken(), sessionFile)
|
||||
})
|
||||
|
||||
// TokenFlag should generate a new session token and store it in the session file.
|
||||
t.Run("TokenFlag", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
|
|
@ -1441,7 +1441,7 @@ func newProvisionerDaemon(
|
|||
|
||||
connector[string(database.ProvisionerTypeTerraform)] = sdkproto.NewDRPCProvisionerClient(terraformClient)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown provisioner type %q", provisionerType)
|
||||
return nil, xerrors.Errorf("unknown provisioner type %q", provisionerType)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -79,6 +80,10 @@ func (r *RootCmd) ssh() *serpent.Command {
|
|||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// Prevent unnecessary logs from the stdlib from messing up the TTY.
|
||||
// See: https://github.com/coder/coder/issues/13144
|
||||
log.SetOutput(io.Discard)
|
||||
|
||||
logger := inv.Logger
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
|
|
|
@ -25,6 +25,7 @@ OPTIONS:
|
|||
--use-token-as-session bool
|
||||
By default, the CLI will generate a new session token when logging in.
|
||||
This flag will instead use the provided token as the session token.
|
||||
See `coder --help` for more information on how to set a token.
|
||||
|
||||
———
|
||||
Run `coder --help` for a list of global options.
|
||||
|
|
|
@ -54,4 +54,4 @@ Specifies whether a trial license should be provisioned for the Coder deployment
|
|||
| ---- | ----------------- |
|
||||
| Type | <code>bool</code> |
|
||||
|
||||
By default, the CLI will generate a new session token when logging in. This flag will instead use the provided token as the session token.
|
||||
By default, the CLI will generate a new session token when logging in. This flag will instead use the provided token as the session token. See `coder --help` for more information on how to set a token.
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
#!/sbin/openrc-run
|
||||
name=coder
|
||||
description="Coder - Self-hosted developer workspaces on your infra"
|
||||
document="https://coder.com/docs/coder-oss"
|
||||
|
||||
depend() {
|
||||
need net
|
||||
after net-online
|
||||
use dns logger
|
||||
}
|
||||
|
||||
checkpath --directory --owner coder:coder --mode 0700 /var/cache/coder
|
||||
|
||||
start_pre() {
|
||||
if [ ! -f /etc/coder.d/coder.env ]; then
|
||||
eerror "/etc/coder.d/coder.env file does not exist"
|
||||
return 1
|
||||
fi
|
||||
# Read and export environment variables ignoring comment lines and blank lines
|
||||
while IFS= read -r line; do
|
||||
# Skip blank or comment lines
|
||||
if [ -z "$line" ] || [[ "$line" =~ ^# ]]; then
|
||||
continue
|
||||
fi
|
||||
export "$line"
|
||||
done < /etc/coder.d/coder.env
|
||||
}
|
||||
|
||||
command="/usr/bin/coder"
|
||||
command_args="server"
|
||||
command_user="coder:coder"
|
||||
command_background="yes"
|
||||
pidfile="/run/coder.pid"
|
||||
|
||||
restart="always"
|
||||
restart_delay="5"
|
||||
|
||||
stop_timeout="90"
|
|
@ -0,0 +1,39 @@
|
|||
#!/sbin/openrc-run
|
||||
name=coder-workspace-proxy
|
||||
description="Coder - external workspace proxy server"
|
||||
document="https://coder.com/docs/coder-oss"
|
||||
|
||||
depend() {
|
||||
need net
|
||||
after net-online
|
||||
use dns logger
|
||||
}
|
||||
|
||||
checkpath --directory --owner coder:coder --mode 0700 /var/cache/coder
|
||||
|
||||
start_pre() {
|
||||
if [ ! -f /etc/coder.d/coder-workspace-proxy.env ]; then
|
||||
eerror "/etc/coder.d/coder-workspace-proxy.env file does not exist"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Read and export environment variables ignoring comment lines and blank lines
|
||||
while IFS= read -r line; do
|
||||
# Skip blank or comment lines
|
||||
if [ -z "$line" ] || [[ "$line" =~ ^# ]]; then
|
||||
continue
|
||||
fi
|
||||
export "$line"
|
||||
done < /etc/coder.d/coder-workspace-proxy.env
|
||||
}
|
||||
|
||||
command="/usr/bin/coder"
|
||||
command_args="workspace-proxy server"
|
||||
command_user="coder:coder"
|
||||
command_background="yes"
|
||||
pidfile="/run/coder-workspace-proxy.pid"
|
||||
|
||||
restart="always"
|
||||
restart_delay="5"
|
||||
|
||||
stop_timeout="90"
|
|
@ -0,0 +1,29 @@
|
|||
name: coder
|
||||
platform: linux
|
||||
arch: "${GOARCH}"
|
||||
version: "${CODER_VERSION}"
|
||||
version_schema: semver
|
||||
release: 1
|
||||
|
||||
vendor: Coder
|
||||
homepage: https://coder.com
|
||||
maintainer: Coder <support@coder.com>
|
||||
description: |
|
||||
Provision development environments with infrastructure with code
|
||||
license: AGPL-3.0
|
||||
suggests:
|
||||
- postgresql
|
||||
|
||||
scripts:
|
||||
preinstall: preinstall.sh
|
||||
|
||||
contents:
|
||||
- src: coder
|
||||
dst: /usr/bin/coder
|
||||
- src: coder.env
|
||||
dst: /etc/coder.d/coder.env
|
||||
type: "config|noreplace"
|
||||
- src: coder-workspace-proxy-openrc
|
||||
dst: /etc/init.d/coder-workspace-proxy
|
||||
- src: coder-openrc
|
||||
dst: /etc/init.d/coder
|
|
@ -89,9 +89,16 @@ ln "$(realpath scripts/linux-pkg/coder.service)" "$temp_dir/"
|
|||
ln "$(realpath scripts/linux-pkg/nfpm.yaml)" "$temp_dir/"
|
||||
ln "$(realpath scripts/linux-pkg/preinstall.sh)" "$temp_dir/"
|
||||
|
||||
nfpm_config_file="nfpm.yaml"
|
||||
|
||||
# Use nfpm-alpine.yaml when building for Alpine (OpenRC).
|
||||
if [[ "$format" == "apk" ]]; then
|
||||
nfpm_config_file="nfpm-alpine.yaml"
|
||||
fi
|
||||
|
||||
pushd "$temp_dir"
|
||||
GOARCH="$arch" CODER_VERSION="$version" nfpm package \
|
||||
-f nfpm.yaml \
|
||||
-f "$nfpm_config_file" \
|
||||
-p "$format" \
|
||||
-t "$output_path" \
|
||||
1>&2
|
||||
|
|
Loading…
Reference in New Issue