-
Notifications
You must be signed in to change notification settings - Fork 162
Expand file tree
/
Copy pathlogin.go
More file actions
166 lines (145 loc) · 4.37 KB
/
login.go
File metadata and controls
166 lines (145 loc) · 4.37 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
package login
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"reflect"
"time"
"github.com/scaleway/scaleway-cli/v2/core"
initCommand "github.com/scaleway/scaleway-cli/v2/internal/namespaces/init"
"github.com/scaleway/scaleway-cli/v2/internal/namespaces/login/webcallback"
iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/logger"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/skratchdot/open-golang/open"
)
func GetCommands() *core.Commands {
return core.NewCommands(loginCommand())
}
type loginArgs struct {
Port int `json:"port"`
// PrintURL will print the account url instead of trying to open it with a browser
PrintURL bool `json:"print_url"`
// ExpiresAt is the expiration date of the API key created during login
ExpiresAt *time.Time `json:"expires_at"`
}
func loginCommand() *core.Command {
return &core.Command{
Groups: []string{"config"},
Short: `Login to scaleway`,
Long: `Start an interactive connection to scaleway to initialize the active profile of the config
A webpage will open while the CLI will wait for a response.
Once you connected to Scaleway, the profile should be configured.
`,
Namespace: "login",
AllowAnonymousClient: true,
ArgsType: reflect.TypeOf(loginArgs{}),
ArgSpecs: core.ArgSpecs{
{
Name: "port",
Short: "The port number used to wait for browser's response",
},
{
Name: "expires-at",
Short: "Expiration date of the API key (ISO 8601 or relative like +1y, +90d). Required when your organization enforces max-api-key-expiration-duration.",
},
},
SeeAlsos: []*core.SeeAlso{
{
Short: "Init profile manually",
Command: "scw init",
},
},
Run: func(ctx context.Context, argsI any) (any, error) {
args := argsI.(*loginArgs)
opts := []webcallback.Options(nil)
if args.Port > 0 {
opts = append(opts, webcallback.WithPort(args.Port))
}
wb := webcallback.New(opts...)
err := wb.Start()
if err != nil {
return nil, err
}
callbackURL := fmt.Sprintf("http://localhost:%d/callback", wb.Port())
accountURL := "https://account.scaleway.com/authenticate?redirectToUrl=" + callbackURL
logger.Debugf("Web server started, waiting for callback on %s\n", callbackURL)
if args.PrintURL {
fmt.Println(accountURL)
} else {
err = open.Start(accountURL)
if err != nil {
logger.Warningf(
"Failed to open web url, you may not have a default browser configured",
)
logger.Warningf("You can open it: " + accountURL)
}
}
fmt.Println("waiting for callback from browser...")
token, err := wb.Wait(ctx)
if err != nil {
return nil, err
}
rawToken, err := base64.StdEncoding.DecodeString(token)
if err != nil {
return nil, err
}
tt := Token{}
err = json.Unmarshal(rawToken, &tt)
if err != nil {
return nil, err
}
client, err := scw.NewClient(scw.WithJWT(tt.Token))
if err != nil {
return nil, err
}
api := iam.NewAPI(client)
req := &iam.CreateAPIKeyRequest{
UserID: &tt.Jwt.AudienceID,
Description: "Generated by the Scaleway CLI",
}
if args.ExpiresAt != nil {
req.ExpiresAt = args.ExpiresAt
}
apiKey, err := api.CreateAPIKey(req)
if err != nil {
return nil, err
}
resp, err := initCommand.Command().Run(ctx, &initCommand.Args{
AccessKey: apiKey.AccessKey,
SecretKey: *apiKey.SecretKey,
ProjectID: apiKey.DefaultProjectID,
OrganizationID: apiKey.DefaultProjectID,
Region: scw.RegionFrPar,
Zone: scw.ZoneFrPar1,
})
if err != nil {
// Cleanup API Key if init failed
logger.Warningf("Init failed, cleaning API key.\n")
cleanErr := api.DeleteAPIKey(&iam.DeleteAPIKeyRequest{
AccessKey: apiKey.AccessKey,
})
if cleanErr != nil {
logger.Warningf("Failed to clean API key: %s\n", err.Error())
}
return nil, err
}
return resp, nil
},
}
}
type Token struct {
Jwt struct {
AudienceID string `json:"audienceId"`
CreatedAt time.Time `json:"createdAt"`
ExpiresAt time.Time `json:"expiresAt"`
IP string `json:"ip"`
IssuerID string `json:"issuerId"`
Jti string `json:"jti"`
UpdatedAt time.Time `json:"updatedAt"`
UserAgent string `json:"userAgent"`
} `json:"jwt"`
RenewToken string `json:"renewToken"`
Token string `json:"token"`
}