github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/client/cli/token/token.go (about) 1 // Package token contains CLI client token related helpers 2 // tToken files consist of one line per token, each token having 3 // the structure of `micro://envAddress/namespace[/id]:token`, ie. 4 // micro://m3o.com/foo-bar-baz/asim@aslam.me:afsafasfasfaceevqcCEWVEWV 5 // or 6 // micro://m3o.com/foo-bar-baz:afsafasfasfaceevqcCEWVEWV 7 package token 8 9 import ( 10 "bytes" 11 "encoding/base64" 12 "encoding/json" 13 "fmt" 14 "io/ioutil" 15 "os" 16 "path/filepath" 17 "strconv" 18 "strings" 19 "time" 20 21 "github.com/tickoalcantara12/micro/v3/client/cli/namespace" 22 "github.com/tickoalcantara12/micro/v3/client/cli/util" 23 "github.com/tickoalcantara12/micro/v3/service/auth" 24 "github.com/tickoalcantara12/micro/v3/util/config" 25 "github.com/tickoalcantara12/micro/v3/util/user" 26 "github.com/urfave/cli/v2" 27 ) 28 29 const tokensFileName = "tokens" 30 31 // Get tries a best effort read of auth token from user config. 32 // Might have missing `RefreshToken` or `Expiry` fields in case of 33 // incomplete or corrupted user config. 34 func Get(ctx *cli.Context) (*auth.AccountToken, error) { 35 tok, err := getFromFile(ctx) 36 if err == nil { 37 return tok, nil 38 } 39 env, err := util.GetEnv(ctx) 40 if err != nil { 41 return nil, err 42 } 43 return getFromUserConfig(env.Name) 44 } 45 46 type token struct { 47 AccessToken string `json:"access_token"` 48 RefreshToken string `json:"refresh_token"` 49 // unix timestamp 50 Created int64 `json:"created"` 51 // unix timestamp 52 Expiry int64 `json:"expiry"` 53 } 54 55 func tokensFilePath() string { 56 return filepath.Join(user.Dir, tokensFileName) 57 } 58 59 func getFromFile(ctx *cli.Context) (*auth.AccountToken, error) { 60 tokens, err := getTokens() 61 if err != nil { 62 return nil, err 63 } 64 65 env, err := util.GetEnv(ctx) 66 if err != nil { 67 return nil, err 68 } 69 // We save the current user 70 userID, err := config.Get(config.Path(env.Name, "current-user")) 71 if err != nil { 72 return nil, err 73 } 74 75 // Look up the token 76 tk, err := tokenKey(ctx, userID) 77 if err != nil { 78 return nil, err 79 } 80 tok, found := tokens[tk] 81 if !found { 82 ns, err := namespace.Get(env.Name) 83 if err != nil { 84 return nil, err 85 } 86 return nil, fmt.Errorf("Can't find token for address %v and namespace %v", env.ProxyAddress, ns) 87 } 88 return &auth.AccountToken{ 89 AccessToken: tok.AccessToken, 90 RefreshToken: tok.RefreshToken, 91 Created: time.Unix(tok.Created, 0), 92 Expiry: time.Unix(tok.Expiry, 0), 93 }, nil 94 } 95 96 func getTokens() (map[string]token, error) { 97 f, err := os.OpenFile(tokensFilePath(), os.O_RDONLY|os.O_CREATE, 0700) 98 if err != nil { 99 return nil, err 100 } 101 dat, err := ioutil.ReadAll(f) 102 if err != nil { 103 return nil, err 104 } 105 lines := strings.Split(string(dat), "\n") 106 ret := map[string]token{} 107 for _, line := range lines { 108 parts := strings.Split(line, ":") 109 if len(parts) < 3 { 110 continue 111 } 112 key := strings.Join(parts[0:len(parts)-1], ":") 113 base64Encoded := parts[len(parts)-1] 114 jsonMarshalled, err := base64.StdEncoding.DecodeString(base64Encoded) 115 if err != nil { 116 return nil, fmt.Errorf("Error base64 decoding token: %v", err) 117 } 118 tok := token{} 119 err = json.Unmarshal(jsonMarshalled, &tok) 120 if err != nil { 121 return nil, fmt.Errorf("Error unmarshalling token: %v", err) 122 } 123 ret[key] = tok 124 } 125 return ret, nil 126 } 127 128 func getFromUserConfig(envName string) (*auth.AccountToken, error) { 129 path := []string{"micro", "auth", envName} 130 accessToken, _ := config.Get(config.Path(append(path, "token")...)) 131 132 refreshToken, err := config.Get(config.Path(append(path, "refresh-token")...)) 133 if err != nil { 134 // Gracefully degrading here in case the user only has a temporary access token at hand. 135 // The call will fail on the receiving end. 136 return &auth.AccountToken{ 137 AccessToken: accessToken, 138 }, nil 139 } 140 141 // See if the access token has expired 142 expiry, _ := config.Get(config.Path(append(path, "expiry")...)) 143 if len(expiry) == 0 { 144 return &auth.AccountToken{ 145 AccessToken: accessToken, 146 RefreshToken: refreshToken, 147 }, nil 148 } 149 expiryInt, err := strconv.ParseInt(expiry, 10, 64) 150 if err != nil { 151 return nil, err 152 } 153 return &auth.AccountToken{ 154 AccessToken: accessToken, 155 RefreshToken: refreshToken, 156 Expiry: time.Unix(expiryInt, 0), 157 }, nil 158 } 159 160 // Save saves the auth token to the user's local config file 161 // Caution: it overwrites $env.current-user with the accountID 162 // that the account token represents. 163 func Save(ctx *cli.Context, token *auth.AccountToken) error { 164 return saveToFile(ctx, token) 165 } 166 167 func tokenKey(ctx *cli.Context, accountID string) (string, error) { 168 env, err := util.GetEnv(ctx) 169 if err != nil { 170 return "", err 171 } 172 ns, err := namespace.Get(env.Name) 173 if err != nil { 174 return "", err 175 } 176 return fmt.Sprintf("micro://%v/%v/%v", env.ProxyAddress, ns, accountID), nil 177 } 178 179 func saveTokens(tokens map[string]token) error { 180 buf := bytes.NewBuffer([]byte{}) 181 for key, t := range tokens { 182 marshalledToken, err := json.Marshal(t) 183 if err != nil { 184 return err 185 } 186 base64Token := base64.StdEncoding.EncodeToString(marshalledToken) 187 _, err = buf.WriteString(key + ":" + base64Token + "\n") 188 if err != nil { 189 return err 190 } 191 } 192 return ioutil.WriteFile(tokensFilePath(), buf.Bytes(), 0700) 193 } 194 195 func saveToFile(ctx *cli.Context, authToken *auth.AccountToken) error { 196 tokens, err := getTokens() 197 if err != nil { 198 return err 199 } 200 account, err := auth.Inspect(authToken.AccessToken) 201 if err != nil { 202 return err 203 } 204 205 env, err := util.GetEnv(ctx) 206 if err != nil { 207 return err 208 } 209 // We save the current user 210 err = config.Set(config.Path(env.Name, "current-user"), account.ID) 211 if err != nil { 212 return err 213 } 214 215 key, err := tokenKey(ctx, account.ID) 216 if err != nil { 217 return err 218 } 219 tokens[key] = token{ 220 AccessToken: authToken.AccessToken, 221 RefreshToken: authToken.RefreshToken, 222 Created: authToken.Created.Unix(), 223 Expiry: authToken.Expiry.Unix(), 224 } 225 return saveTokens(tokens) 226 } 227 228 func saveToUserConfig(envName string, token *auth.AccountToken) error { 229 if err := config.Set(config.Path("micro", "auth", envName, "token"), token.AccessToken); err != nil { 230 return err 231 } 232 // Store the refresh token in micro config 233 if err := config.Set(config.Path("micro", "auth", envName, "refresh-token"), token.RefreshToken); err != nil { 234 return err 235 } 236 // Store the refresh token in micro config 237 return config.Set(config.Path("micro", "auth", envName, "expiry"), fmt.Sprintf("%v", token.Expiry.Unix())) 238 } 239 240 // Remove deletes a token. Useful when trying to reset test 241 // for example at testing: not having a token is a different state 242 // than having an invalid token. 243 func Remove(ctx *cli.Context) error { 244 env, err := util.GetEnv(ctx) 245 if err != nil { 246 return err 247 } 248 // intentionally ignoring the errors here 249 removeFromUserConfig(env.Name) 250 return removeFromFile(ctx) 251 } 252 253 func removeFromFile(ctx *cli.Context) error { 254 tokens, err := getTokens() 255 if err != nil { 256 return err 257 } 258 env, err := util.GetEnv(ctx) 259 if err != nil { 260 return err 261 } 262 userID, err := config.Get(config.Path(env.Name, "current-user")) 263 if err != nil { 264 return err 265 } 266 key, err := tokenKey(ctx, userID) 267 if err != nil { 268 return err 269 } 270 delete(tokens, key) 271 return saveTokens(tokens) 272 } 273 274 func removeFromUserConfig(envName string) error { 275 if err := config.Set(config.Path("micro", "auth", envName, "token"), ""); err != nil { 276 return err 277 } 278 // Store the refresh token in micro config 279 if err := config.Set(config.Path("micro", "auth", envName, "refresh-token"), ""); err != nil { 280 return err 281 } 282 // Store the refresh token in micro config 283 return config.Set(config.Path("micro", "auth", envName, "expiry"), "") 284 }