-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathconfig.go
More file actions
265 lines (224 loc) · 7.93 KB
/
config.go
File metadata and controls
265 lines (224 loc) · 7.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
package common
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
"github.com/dgrijalva/jwt-go"
homedir "github.com/mitchellh/go-homedir"
"github.com/provideplatform/provide-go/api/ident"
"github.com/spf13/viper"
)
const (
ASCIIBanner = `██████╗ ██████╗ ██████╗ ██╗ ██╗██╗██████╗ ███████╗
██╔══██╗██╔══██╗██╔═══██╗██║ ██║██║██╔══██╗██╔════╝
██████╔╝██████╔╝██║ ██║██║ ██║██║██║ ██║█████╗
██╔═══╝ ██╔══██╗██║ ██║╚██╗ ██╔╝██║██║ ██║██╔══╝
██║ ██║ ██║╚██████╔╝ ╚████╔╝ ██║██████╔╝███████╗
╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═══╝ ╚═╝╚═════╝ ╚══════╝`
// Viper downcases key names, so hyphenating for better readability.
// 'Partial' keys are to be combined with the application ID they are associated with.
// and NOT used by themselves.
AccessTokenConfigKey = "access-token" // user-scoped API access token key
RefreshTokenConfigKey = "refresh-token" // user-scoped API refresh token key
APIAccessTokenConfigKeyPartial = "api-token" // app- or org-scoped API token key
APIRefreshTokenConfigKeyPartial = "api-refresh-token" // app- or org-scoped API token key
AccountConfigKeyPartial = "account" // app-scoped account ID key
OrganizationConfigKeyPartial = "organization" // app-scoped organization ID key
WalletConfigKeyPartial = "wallet" // app-scoped HD wallet ID key
UserInfoConfigKey = "prvd-user-info" // details of the currently auth'd user
)
var CfgFile string
// initConfig reads in config file and ENV variables if set.
func InitConfig() {
if CfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(CfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// Search config in home directory with name ".provide-cli" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".provide-cli")
configPath := fmt.Sprintf("%s/.provide-cli.yaml", home)
if err := viper.SafeWriteConfigAs(configPath); err != nil {
if os.IsNotExist(err) {
err = viper.WriteConfigAs(configPath)
if err != nil {
fmt.Printf("WARNING: failed to write configuration; %s", err.Error())
}
}
}
}
viper.AutomaticEnv() // read in environment variables that match
err := viper.ReadInConfig()
if err != nil {
fmt.Printf("WARNING: failed to read configuration; %s", err.Error())
} else {
os.Chmod(viper.ConfigFileUsed(), 0600)
if Verbose {
fmt.Println("Using configuration:", viper.ConfigFileUsed())
}
}
}
func StoreUserDetails(user *ident.User) error {
json, err := json.Marshal(user)
if err != nil {
log.Printf("Error storing user details; %s", err)
return err
}
viper.Set(UserInfoConfigKey, string(json))
viper.WriteConfig()
return nil
}
func GetUserDetails() (*ident.User, error) {
if viper.IsSet(UserInfoConfigKey) {
raw := viper.GetString(UserInfoConfigKey)
var user ident.User
err := json.Unmarshal([]byte(raw), &user)
if err != nil {
return nil, err
}
return &user, nil
}
return nil, fmt.Errorf("please authenticate to retrieve your user info")
}
func RequireUserAccessToken() string {
token := ""
if viper.IsSet(AccessTokenConfigKey) {
token = viper.GetString(AccessTokenConfigKey)
}
if token == "" {
log.Printf("Authorized API access token required in prvd configuration; run 'authenticate'")
os.Exit(1)
}
if isTokenExpired(token) {
refreshToken()
token = viper.GetString(AccessTokenConfigKey)
}
return token
}
func refreshToken() {
refreshToken := ""
if viper.IsSet(RefreshTokenConfigKey) {
refreshToken = viper.GetString(RefreshTokenConfigKey)
}
resp, err := ident.CreateToken(refreshToken, map[string]interface{}{
"grant_type": "refresh_token",
})
if err != nil {
log.Println(err)
os.Exit(1)
}
if resp != nil {
CacheAccessRefreshToken(resp)
}
}
func CacheAccessRefreshToken(token *ident.Token) {
if token.AccessToken != nil {
viper.Set(AccessTokenConfigKey, *token.AccessToken)
}
if token.RefreshToken != nil {
viper.Set(RefreshTokenConfigKey, *token.RefreshToken)
}
viper.WriteConfig()
}
func RequireApplicationToken() string {
var token string
tokenKey := BuildConfigKeyWithApp(APIAccessTokenConfigKeyPartial, ApplicationID)
if viper.IsSet(tokenKey) {
token = viper.GetString(tokenKey)
}
if token == "" {
log.Printf("Authorized application API token required in prvd configuration; run 'prvd api_tokens init --application <id>'")
os.Exit(1)
}
return token
}
func RequireOrganizationToken() string {
var token string
tokenKey := BuildConfigKeyWithOrg(APIAccessTokenConfigKeyPartial, OrganizationID)
if viper.IsSet(tokenKey) {
token = viper.GetString(tokenKey)
}
if token == "" {
log.Printf("Authorized organization API token required in prvd configuration; run 'prvd api_tokens init --organization <id>'")
os.Exit(1)
}
return token
}
func RequireAPIToken() string {
var token string
var appAPITokenKey string
var orgAPITokenKey string
if ApplicationID != "" {
appAPITokenKey = BuildConfigKeyWithApp(APIAccessTokenConfigKeyPartial, ApplicationID)
} else if OrganizationID != "" {
orgAPITokenKey = BuildConfigKeyWithOrg(APIAccessTokenConfigKeyPartial, OrganizationID)
}
if viper.IsSet(appAPITokenKey) {
token = viper.GetString(appAPITokenKey)
} else if viper.IsSet(orgAPITokenKey) {
token = viper.GetString(orgAPITokenKey)
} else {
token = RequireUserAccessToken()
}
if token == "" {
log.Printf("Authorized API access token required in prvd configuration; run 'authenticate'")
os.Exit(1)
}
return token
}
// BuildConfigKeyWithApp combines the given key partial and app ID according to a consistent convention.
// Returns an empty string if the given appID is empty.
// Viper's getters likewise return empty strings when passed an empty string.
func BuildConfigKeyWithApp(keyPartial, appID string) string {
if appID == "" {
// Development-time debugging.
log.Println("An application identifier is required for this operation")
return ""
}
return fmt.Sprintf("%s.%s", appID, keyPartial)
}
// BuildConfigKeyWithOrg combines the given key partial and org ID according to a consistent convention.
// Returns an empty string if the given orgID is empty.
// Viper's getters likewise return empty strings when passed an empty string.
func BuildConfigKeyWithOrg(keyPartial, orgID string) string {
if orgID == "" {
// Development-time debugging.
log.Println("An organization identifier is required for this operation")
return ""
}
return fmt.Sprintf("%s.%s", orgID, keyPartial)
}
// BuildConfigKeyWithUser combines the given key partial and user ID according to a consistent convention.
// Returns an empty string if the given userID is empty.
// Viper's getters likewise return empty strings when passed an empty string.
func BuildConfigKeyWithUser(keyPartial, userID string) string {
if userID == "" {
// Development-time debugging.
log.Println("A user identifier is required for this operation")
return ""
}
return fmt.Sprintf("%s.%s", userID, keyPartial)
}
func isTokenExpired(bearerToken string) bool {
var jwtParser jwt.Parser
token, _, err := jwtParser.ParseUnverified(bearerToken, jwt.MapClaims{})
if err != nil {
log.Printf("failed to parse JWT token on behalf of authorized user; %s", err.Error())
os.Exit(1)
}
claims := token.Claims.(jwt.MapClaims)
if exp, expOk := claims["exp"].(int64); expOk {
expTime := time.Unix(exp, 0)
now := time.Now()
return expTime.Equal(now) || expTime.After(now)
}
return false
}