Skip to content
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
fa512bc
Fix JWT token checks
Aug 10, 2023
4293e23
Fix error pkg
GeorgiYosifov Aug 14, 2023
56fc1e5
Revoke argocd login token
GeorgiYosifov Aug 15, 2023
24a3bbc
Remove duplicated test
GeorgiYosifov Aug 16, 2023
35677d6
Refactor implementation and check exp claim existens
GeorgiYosifov Aug 16, 2023
928762d
Add tests for utils
GeorgiYosifov Aug 18, 2023
97b5e11
Run lint
GeorgiYosifov Aug 18, 2023
a0bf6e6
Fix test
GeorgiYosifov Aug 18, 2023
b453d70
Merge branch 'master' into token-revoked
GeorgiYosifov Aug 28, 2023
ee14927
Lint
GeorgiYosifov Aug 28, 2023
9b8e541
Remove logout test
GeorgiYosifov Aug 28, 2023
d5bb535
Fix e2e test
GeorgiYosifov Aug 28, 2023
0a7c0f1
Add unit tests for logout functions
GeorgiYosifov Aug 29, 2023
c5301aa
Merge branch 'master' into token-revoked
GeorgiYosifov Aug 29, 2023
39b5243
Add Dex token revocation
GeorgiYosifov Aug 30, 2023
a4618be
Adopt tests
GeorgiYosifov Aug 30, 2023
7c98a72
Adopt tests
GeorgiYosifov Aug 31, 2023
ac2b9b5
Add dex logout test
GeorgiYosifov Aug 31, 2023
6648a92
Merge branch 'master' into token-revoked
GeorgiYosifov Aug 31, 2023
e3c6364
Fix tls check
GeorgiYosifov Sep 1, 2023
1a0be29
Fix insecure and non-tls options
GeorgiYosifov Sep 1, 2023
327490a
Merge branch 'master' into token-revoked
GeorgiYosifov Sep 18, 2023
a55f129
Merge branch 'master' into token-revoked
GeorgiYosifov Oct 2, 2023
12d668a
Merge branch 'master' into token-revoked
GeorgiYosifov Oct 5, 2023
7fcb68f
Merge branch 'master' into token-revoked
GeorgiYosifov Oct 22, 2023
7adca52
Merge branch 'master' into token-revoked
GeorgiYosifov Nov 24, 2023
d42c9ad
Merge branch 'master' into token-revoked
GeorgiYosifov Dec 11, 2023
dfa649d
Merge branch 'master' into token-revoked
GeorgiYosifov May 7, 2025
6af5ad8
Get id from MapClaims
GeorgiYosifov May 7, 2025
b24b41c
Change to jti
GeorgiYosifov May 7, 2025
2637a2e
Merge branch 'master' into token-revoked
GeorgiYosifov May 7, 2025
f148828
Revert file
GeorgiYosifov May 7, 2025
84e599d
Change to const
GeorgiYosifov May 7, 2025
f2628ef
Fix lint
GeorgiYosifov May 7, 2025
3d957d2
Fix lint
GeorgiYosifov May 7, 2025
7e22d09
Fix test
GeorgiYosifov May 8, 2025
7a4e873
Merge branch 'master' into token-revoked
GeorgiYosifov May 8, 2025
71748bf
Remove old test
GeorgiYosifov May 12, 2025
e202055
Merge branch 'master' into token-revoked
GeorgiYosifov May 12, 2025
ff685c4
Fix log
GeorgiYosifov May 13, 2025
6cbd501
Merge branch 'master' into token-revoked
GeorgiYosifov May 13, 2025
fc90a95
Merge branch 'master' into token-revoked
GeorgiYosifov May 22, 2025
c656b3a
Merge branch 'master' into token-revoked
GeorgiYosifov Nov 24, 2025
00f6c2b
Fix lint
GeorgiYosifov Nov 24, 2025
1c80b69
Fix lint
GeorgiYosifov Nov 24, 2025
904d927
Fix lint
GeorgiYosifov Nov 24, 2025
bc98b19
Merge branch 'master' into token-revoked
GeorgiYosifov Nov 25, 2025
5853c0b
Revert logout_test
GeorgiYosifov Nov 25, 2025
bdf0271
Remove logout_test
GeorgiYosifov Nov 25, 2025
694219f
Merge branch 'master' into token-revoked
GeorgiYosifov Nov 26, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ cmd/argocd/argocd
cmd/argocd-application-controller/argocd-application-controller
cmd/argocd-repo-server/argocd-repo-server
cmd/argocd-server/argocd-server
cmd/argocd/commands/testdata/local.config
57 changes: 57 additions & 0 deletions cmd/argocd/commands/logout.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package commands

import (
"crypto/tls"
"fmt"
"net/http"
"os"
"strings"
"time"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/argoproj/argo-cd/v2/common"
argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
"github.com/argoproj/argo-cd/v2/util/cli"
"github.com/argoproj/argo-cd/v2/util/errors"
grpc_util "github.com/argoproj/argo-cd/v2/util/grpc"
"github.com/argoproj/argo-cd/v2/util/localconfig"
)

Expand All @@ -35,6 +42,55 @@ $ argocd logout
log.Fatalf("Nothing to logout from")
}

token := localCfg.GetToken(context)
if token == "" {
log.Fatalf("Error in getting token from context")
}
Comment on lines +52 to +55
Copy link
Author

@GeorgiYosifov GeorgiYosifov Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting JWT token for the current context from the file system at local config file.


client := &http.Client{}

dialTime := 30 * time.Second
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this a constant and maybe configurable.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

tlsTestResult, err := grpc_util.TestTLS(context, dialTime)
errors.CheckError(err)
if !tlsTestResult.TLS {
if !globalClientOpts.PlainText {
if !cli.AskToProceed("WARNING: server is not configured with TLS. Proceed (y/n)? ") {
os.Exit(1)
}
globalClientOpts.PlainText = true
}
} else if tlsTestResult.InsecureErr != nil {
if !globalClientOpts.Insecure {
if !cli.AskToProceed(fmt.Sprintf("WARNING: server certificate had error: %s. Proceed insecurely (y/n)? ", tlsTestResult.InsecureErr)) {
os.Exit(1)
}
globalClientOpts.Insecure = true
}
}

scheme := "https"
if globalClientOpts.PlainText {
scheme = strings.TrimSuffix(scheme, "s")
} else if globalClientOpts.Insecure {
client.Transport = &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am reusing logic for TLS determination and scheme to be "http" or "https" for the /logout request below. Logic is taken from:

dialTime := 30 * time.Second
tlsTestResult, err := grpc_util.TestTLS(server, dialTime)
errors.CheckError(err)
if !tlsTestResult.TLS {
if !globalClientOpts.PlainText {
if !cli.AskToProceed("WARNING: server is not configured with TLS. Proceed (y/n)? ") {
os.Exit(1)
}
globalClientOpts.PlainText = true
}
} else if tlsTestResult.InsecureErr != nil {
if !globalClientOpts.Insecure {
if !cli.AskToProceed(fmt.Sprintf("WARNING: server certificate had error: %s. Proceed insecurely (y/n)? ", tlsTestResult.InsecureErr)) {
os.Exit(1)
}
globalClientOpts.Insecure = true
}
}


logoutURL := fmt.Sprintf("%s://%s%s", scheme, context, common.LogoutEndpoint)
req, err := http.NewRequest("POST", logoutURL, nil)
errors.CheckError(err)
cookie := &http.Cookie{
Name: common.AuthCookieName,
Value: token,
}
req.AddCookie(cookie)

_, err = client.Do(req)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This http call needs to be made after reconfirming from the user, needs to be moved after checking thecanLogout.

errors.CheckError(err)
Comment on lines 88 to 98
Copy link
Author

@GeorgiYosifov GeorgiYosifov Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalidate the current token with /logout request, mostly used because it adds the token's ID to the Redis in a revokation list with the remaining revoked tokens.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method will cause a fatal error and cause the program to terminate abruptly. If the remote server is unavailable or errors out due to network issue, it is probably its better to give a warning and logout locally and clean up the local storage.


ok := localCfg.RemoveToken(context)
if !ok {
log.Fatalf("Context %s does not exist", context)
Expand All @@ -44,6 +100,7 @@ $ argocd logout
if err != nil {
log.Fatalf("Error in logging out: %s", err)
}

err = localconfig.WriteLocalConfig(*localCfg, globalClientOpts.ConfigPath)
errors.CheckError(err)

Expand Down
39 changes: 0 additions & 39 deletions cmd/argocd/commands/logout_test.go

This file was deleted.

7 changes: 6 additions & 1 deletion server/logout/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,19 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

issuer := jwtutil.StringField(mapClaims, "iss")
id := jwtutil.StringField(mapClaims, "jti")
// Workaround for Dex token, because does not have jti.
if id == "" {
id = jwtutil.StringField(mapClaims, "at_hash")
}
Comment on lines +110 to +113
Copy link
Author

@GeorgiYosifov GeorgiYosifov Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using "at_hash" as "jti" because Dex token does not have a "jti" and it leads to not working revokation of Dex tokens. I was not able to add "jti" to the Dex token when it is created.


if exp, err := jwtutil.ExpirationTime(mapClaims); err == nil && id != "" {
if err := h.revokeToken(context.Background(), id, time.Until(exp)); err != nil {
log.Warnf("failed to invalidate token '%s': %v", id, err)
}
}

issuer := jwtutil.StringField(mapClaims, "iss")
if argoCDSettings.OIDCConfig() == nil || argoCDSettings.OIDCConfig().LogoutURL == "" || issuer == session.SessionManagerClaimsIssuer {
http.Redirect(w, r, logoutRedirectURL, http.StatusSeeOther)
} else {
Expand Down
63 changes: 61 additions & 2 deletions server/logout/logout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ var (
baseLogoutURLwithRedirectURL = "http://localhost:4000/logout?post_logout_redirect_uri={{logoutRedirectURL}}"
baseLogoutURLwithTokenAndRedirectURL = "http://localhost:4000/logout?id_token_hint={{token}}&post_logout_redirect_uri={{logoutRedirectURL}}"
invalidToken = "sample-token"
dexToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ijc5YTFlNTgzOWM2ZTRjMTZlOTIwYzM1YTU0MmMwNmZhIn0.eyJzdWIiOiIwMHVqNnM1NDVyNU5peVNLcjVkNSIsIm5hbWUiOiJqZCByIiwiZW1haWwiOiJqYWlkZWVwMTdydWx6QGdtYWlsLmNvbSIsInZlciI6MSwiaXNzIjoiaHR0cHM6Ly9kZXYtNTY5NTA5OC5va3RhLmNvbSIsImF1ZCI6IjBvYWowM2FmSEtqN3laWXJwNWQ1IiwiaWF0IjoxNjA1NTcyMzU5LCJleHAiOjE2MDU1NzU5NTksImFtciI6WyJwd2QiXSwiaWRwIjoiMDBvaWdoZmZ2SlFMNjNaOGg1ZDUiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqYWlkZWVwMTdydWx6QGdtYWlsLmNvbSIsImF1dGhfdGltZSI6MTYwNTU3MjM1NywiYXRfaGFzaCI6ImplUTBGaXZqT2c0YjZNSldEMjE5bGcifQ.Xt_5G-4dNZef1egOYmvruszudlAvUXVQzqrI4YwkWJeZ0zZDk4lyhPUVuxVGjB3pCCUCUMloTL6xC7IVFNj53Eb7WNH_hxsFqemJ80HZYbUpo2G9fMjkPmFTaeFVMC4p3qxIaBAT9_uJbTRSyRGYLV-95KDpU-GNDFXlbFq-2bVvhppiYmKszyHbREZkB87Pi7K3Bk0NxAlDOJ7O5lhwjpwuOJ1WGCJptUetePm5MnpVT2ZCyjvntlzwHlIhMSKNlFZuFS_JMca5Ww0fQSBUlarQU9MMyZKBw-QuD5sJw3xjwQpxOG-T9mJz7F8VA5znLi_LJNutHVgcpt3T_TW_0NbgqsHe8Lw"
oidcToken = "eyJraWQiOiJYQi1MM3ZFdHhYWXJLcmRSQnVEV0NwdnZsSnk3SEJVb2d5N253M1U1Z1ZZIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwMHVqNnM1NDVyNU5peVNLcjVkNSIsIm5hbWUiOiJqZCByIiwiZW1haWwiOiJqYWlkZWVwMTdydWx6QGdtYWlsLmNvbSIsInZlciI6MSwiaXNzIjoiaHR0cHM6Ly9kZXYtNTY5NTA5OC5va3RhLmNvbSIsImF1ZCI6IjBvYWowM2FmSEtqN3laWXJwNWQ1IiwiaWF0IjoxNjA1NTcyMzU5LCJleHAiOjE2MDU1NzU5NTksImp0aSI6IklELl9ORDJxVG5iREFtc3hIZUt2U2ZHeVBqTXRicXFEQXdkdlRQTDZCTnpfR3ciLCJhbXIiOlsicHdkIl0sImlkcCI6IjAwb2lnaGZmdkpRTDYzWjhoNWQ1IiwicHJlZmVycmVkX3VzZXJuYW1lIjoiamFpZGVlcDE3cnVsekBnbWFpbC5jb20iLCJhdXRoX3RpbWUiOjE2MDU1NzIzNTcsImF0X2hhc2giOiJqZVEwRml2ak9nNGI2TUpXRDIxOWxnIn0.GHkqwXgW-lrAhJdypW7SVjW0YdNLFQiRL8iwgT6DHJxP9Nb0OtkH2NKcBYAA5N6bTPLRQUHgYwWcgm5zSXmvqa7ciIgPF3tiQI8UmJA9VFRRDR-x9ExX15nskCbXfiQ67MriLslUrQUyzSCfUrSjXKwnDxbKGQncrtmRsh5asfCzJFb9excn311W9HKbT3KA0Ot7eOMnVS6V7SGfXxnKs6szcXIEMa_FhB4zDAVLr-dnxvSG_uuWcHrAkLTUVhHbdQQXF7hXIEfyr5lkMJN-drjdz-bn40GaYulEmUvO1bjcL9toCVQ3Ismypyr0b8phj4w3uRsLDZQxTxK7jAXlyQ"
nonOidcToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MDU1NzQyMTIsImlzcyI6ImFyZ29jZCIsIm5iZiI6MTYwNTU3NDIxMiwic3ViIjoiYWRtaW4ifQ.zDJ4piwWnwsHON-oPusHMXWINlnrRDTQykYogT7afeE"
expectedNonOIDCLogoutURL = "http://localhost:4000"
expectedDexLogoutURL = "http://localhost:4000"
expectedOIDCLogoutURL = "https://dev-5695098.okta.com/oauth2/v1/logout?id_token_hint=" + oidcToken + "&post_logout_redirect_uri=" + baseURL
expectedOIDCLogoutURLWithRootPath = "https://dev-5695098.okta.com/oauth2/v1/logout?id_token_hint=" + oidcToken + "&post_logout_redirect_uri=" + baseURL + "/" + rootPath
)
Expand Down Expand Up @@ -85,6 +87,40 @@ func TestConstructLogoutURL(t *testing.T) {
}

func TestHandlerConstructLogoutURL(t *testing.T) {
kubeClientWithDexConfig := fake.NewSimpleClientset(
&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDConfigMapName,
Namespace: "default",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
Data: map[string]string{
"dex.config": "connectors: \n" +
"- type: dev \n" +
"name: Dev \n" +
"config: \n" +
"issuer: https://dev-5695098.okta.com \n" +
"clientID: aabbccddeeff00112233 \n" +
"clientSecret: aabbccddeeff00112233",
"url": "http://localhost:4000",
},
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: common.ArgoCDSecretName,
Namespace: "default",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
Data: map[string][]byte{
"admin.password": nil,
"server.secretkey": nil,
},
},
)
kubeClientWithOIDCConfig := fake.NewSimpleClientset(
&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -208,13 +244,23 @@ func TestHandlerConstructLogoutURL(t *testing.T) {
},
)

settingsManagerWithDexConfig := settings.NewSettingsManager(context.Background(), kubeClientWithDexConfig, "default")
settingsManagerWithOIDCConfig := settings.NewSettingsManager(context.Background(), kubeClientWithOIDCConfig, "default")
settingsManagerWithoutOIDCConfig := settings.NewSettingsManager(context.Background(), kubeClientWithoutOIDCConfig, "default")
settingsManagerWithOIDCConfigButNoLogoutURL := settings.NewSettingsManager(context.Background(), kubeClientWithOIDCConfigButNoLogoutURL, "default")
settingsManagerWithOIDCConfigButNoURL := settings.NewSettingsManager(context.Background(), kubeClientWithOIDCConfigButNoURL, "default")

sessionManager := session.NewSessionManager(settingsManagerWithOIDCConfig, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
redisClient, closer := test.NewInMemoryRedis()
defer closer()
sessionManager := session.NewSessionManager(settingsManagerWithOIDCConfig, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(redisClient))

dexHandler := NewHandler(appclientset.NewSimpleClientset(), settingsManagerWithDexConfig, sessionManager, rootPath, baseHRef, "default")
dexHandler.verifyToken = func(tokenString string) (jwt.Claims, string, error) {
if !validJWTPattern.MatchString(tokenString) {
return nil, "", errors.New("invalid jwt")
}
return &jwt.RegisteredClaims{Issuer: "dev"}, "", nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issuer: "dev"

Is this intended?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is used for testing.

}
oidcHandler := NewHandler(appclientset.NewSimpleClientset(), settingsManagerWithOIDCConfig, sessionManager, rootPath, baseHRef, "default")
oidcHandler.verifyToken = func(tokenString string) (jwt.Claims, string, error) {
if !validJWTPattern.MatchString(tokenString) {
Expand All @@ -236,21 +282,26 @@ func TestHandlerConstructLogoutURL(t *testing.T) {
}
return &jwt.RegisteredClaims{Issuer: "okta"}, "", nil
}

oidcHandlerWithoutBaseURL := NewHandler(appclientset.NewSimpleClientset(), settingsManagerWithOIDCConfigButNoURL, sessionManager, "argocd", baseHRef, "default")
oidcHandlerWithoutBaseURL.verifyToken = func(tokenString string) (jwt.Claims, string, error) {
if !validJWTPattern.MatchString(tokenString) {
return nil, "", errors.New("invalid jwt")
}
return &jwt.RegisteredClaims{Issuer: "okta"}, "", nil
}

dexTokenHeader := make(map[string][]string)
dexTokenHeader["Cookie"] = []string{"argocd.token=" + dexToken}
oidcTokenHeader := make(map[string][]string)
oidcTokenHeader["Cookie"] = []string{"argocd.token=" + oidcToken}
nonOidcTokenHeader := make(map[string][]string)
nonOidcTokenHeader["Cookie"] = []string{"argocd.token=" + nonOidcToken}
invalidHeader := make(map[string][]string)
invalidHeader["Cookie"] = []string{"argocd.token=" + invalidToken}

dexRequest, err := http.NewRequest(http.MethodGet, "http://localhost:4000/api/logout", nil)
assert.NoError(t, err)
dexRequest.Header = dexTokenHeader
oidcRequest, err := http.NewRequest(http.MethodGet, "http://localhost:4000/api/logout", nil)
assert.NoError(t, err)
oidcRequest.Header = oidcTokenHeader
Expand All @@ -273,6 +324,14 @@ func TestHandlerConstructLogoutURL(t *testing.T) {
expectedLogoutURL string
wantErr bool
}{
{
name: "Case: Dex logout request with valid token",
handler: dexHandler,
request: dexRequest,
responseRecorder: httptest.NewRecorder(),
expectedLogoutURL: expectedDexLogoutURL,
wantErr: false,
},
{
name: "Case: OIDC logout request with valid token",
handler: oidcHandler,
Expand Down
2 changes: 1 addition & 1 deletion server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ func TestAuthenticate(t *testing.T) {
argocd := NewServer(context.Background(), argoCDOpts)
ctx := context.Background()
if testData.user != "" {
token, err := argocd.sessionMgr.Create(testData.user, 0, "abc")
token, err := argocd.sessionMgr.Create(testData.user, int64(5*time.Minute), "abc")
assert.NoError(t, err)
ctx = metadata.NewIncomingContext(context.Background(), metadata.Pairs(apiclient.MetaDataTokenKey, token))
}
Expand Down
15 changes: 4 additions & 11 deletions test/e2e/accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import (
"testing"

"github.com/argoproj/pkg/errors"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
"github.com/argoproj/argo-cd/v2/pkg/apiclient/account"
"github.com/argoproj/argo-cd/v2/pkg/apiclient/session"
. "github.com/argoproj/argo-cd/v2/test/e2e/fixture"
Expand Down Expand Up @@ -134,17 +132,12 @@ test true login, apiKey`, output)
token, err := RunCli("account", "generate-token", "--account", "test")
errors.CheckError(err)

clientOpts := ArgoCDClientset.ClientOptions()
clientOpts.AuthToken = token
testAccountClientset := headless.NewClientOrDie(&clientOpts, &cobra.Command{})
assert.NotEmpty(t, token)

closer, client := testAccountClientset.NewSessionClientOrDie()
defer io.Close(closer)

info, err := client.GetUserInfo(context.Background(), &session.GetUserInfoRequest{})
assert.NoError(t, err)
name, err := RunCli("account", "get", "--account", "test", "--output", "name")
errors.CheckError(err)

assert.Equal(t, info.Username, "test")
assert.Equal(t, "test", name)
}

func TestLoginBadCredentials(t *testing.T) {
Expand Down
9 changes: 9 additions & 0 deletions util/localconfig/localconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,15 @@ func (l *LocalConfig) RemoveUser(serverName string) bool {
return false
}

func (l *LocalConfig) GetToken(serverName string) string {
for _, u := range l.Users {
if u.Name == serverName {
return u.AuthToken
}
}
return ""
}

// Returns true if user was removed successfully
func (l *LocalConfig) RemoveToken(serverName string) bool {
for i, u := range l.Users {
Expand Down
Loading