diff --git a/auth_request.go b/auth_request.go index 27b267f..2b251ac 100644 --- a/auth_request.go +++ b/auth_request.go @@ -22,3 +22,7 @@ func (r *AuthRequest) SetId(ID string) error { func (r *AuthRequest) Cancel() { r.Client.Close() } + +func (r *AuthRequest) FulFill(t *AuthToken) error { + return r.Client.FulFillRequest(t) +} diff --git a/auth_request_client.go b/auth_request_client.go index 47f9af1..9b016de 100644 --- a/auth_request_client.go +++ b/auth_request_client.go @@ -13,6 +13,7 @@ const ( MSG_TYPE_AUTH string = "auth" MSG_TYPE_SET_ID string = "set-id" MSG_TYPE_INVALID_HOST string = "invalid-host" + MSG_TYPE_FULFILL string = "fulfill" ) type AuthRequestClient struct { @@ -94,15 +95,24 @@ func (c *AuthRequestClient) PropagateID(ID string) error { }, } - err := c.conn.WriteJSON(message) - - return err + return c.conn.WriteJSON(message) } func (c *AuthRequestClient) Close() { c.conn.Close() } +func (c *AuthRequestClient) FulFillRequest(token *AuthToken) error { + message := &AuthRequestProtocolMessage{ + MessageType: MSG_TYPE_FULFILL, + Parameters: map[string]string{ + "token": token.Token, + }, + } + + return c.conn.WriteJSON(message) +} + func validHost(host string) bool { re, _ := regexp.Compile(`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`) diff --git a/auth_token.go b/auth_token.go deleted file mode 100644 index e1e4b25..0000000 --- a/auth_token.go +++ /dev/null @@ -1,6 +0,0 @@ -package main - -type AuthToken struct { - RequestID string - Token string -} diff --git a/authentication_map.go b/authentication_map.go index bf5585f..e34b3ac 100644 --- a/authentication_map.go +++ b/authentication_map.go @@ -82,3 +82,9 @@ func (q *AuthenticationMap) startRequestMapCleaner() { } }() } + +func (q *AuthenticationMap) Delete(ID string) { + q.requestMapMutex.Lock() + delete(q.requestMap, ID) + q.requestMapMutex.Unlock() +} diff --git a/main.go b/main.go index c0b49ad..890b9c0 100644 --- a/main.go +++ b/main.go @@ -29,6 +29,7 @@ var ( type Instance struct { Secret string `json:"client_secret"` ID string `json:"client_id"` + Host string } type App struct { @@ -47,7 +48,14 @@ type AppConfig struct { Website string `yaml:"website"` } -func (a *App) authRequestWebSocket(c echo.Context) error { +type AuthToken struct { + RequestID string + Token string `json:"access_token"` + Error string `json:"error"` + ErrorDescription string `json:"error_description"` +} + +func (a *App) AuthRequestWebSocket(c echo.Context) error { ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil) if err != nil { @@ -71,11 +79,119 @@ func (a *App) authRequestWebSocket(c echo.Context) error { return nil } -func (a *App) GetInstanceDataByRequest(request *AuthRequest) (*Instance, error) { +func (a *App) AuthRequestConfirm(c echo.Context) error { + code := c.QueryParam("code") + reqID := c.QueryParam("state") + + if code == "" || reqID == "" { + return c.String( + http.StatusUnprocessableEntity, + "Could not process request, missing required parameters", + ) + } + + authRequest := a.AuthMap.GetRequestByID(reqID) + + if authRequest == nil { + (*a.Logger).Errorf("Could not find auth request by ID %s", reqID) + c.String(http.StatusNotFound, "Auth request with given ID not found") + } + + instance, err := a.GetCredentialsForInstanceHost(authRequest.Instance) + + if err != nil { + (*a.Logger).Errorf( + "Was not able to retrieve instance credentials for host %s during confirm action: %s", + authRequest.Instance, + err, + ) + + return c.String( + http.StatusInternalServerError, + "Something has gone wrong, please note down the current time and contact the server admin", + ) + } + + token, err := a.GetAuthTokenForAuthorizationCode(code, instance) + + if err != nil { + (*a.Logger).Errorf( + "Failed to retreive acces token from instance %s: %s", + instance.Host, + err, + ) + + return c.String( + http.StatusOK, + "Something went wrong while retrieving the access token. Please contact the server admin", + ) + } + + err = authRequest.FulFill(token) + a.AuthMap.Delete(authRequest.ID) + + if err != nil { + (*a.Logger).Errorf( + "Unable to fulfill authentication request with ID %s : %s", + authRequest.ID, + err, + ) + return c.String( + http.StatusInternalServerError, + "Something went wrong with the connection to your client", + ) + } + + return c.String(http.StatusOK, "Athentication completed, you can close this window now") +} + +func (a *App) GetAuthTokenForAuthorizationCode(code string, instance *Instance) (*AuthToken, error) { + data := map[string]string{ + "client_id": instance.ID, + "client_secret": instance.Secret, + "code": code, + "grant_type": "authorization_code", + "redirect_uri": a.Config.AppScheme + "://" + a.Config.AppHost + "/confirm", + } + + jsonData, err := json.Marshal(data) + + if err != nil { + return nil, err + } + + response, err := http.Post("https://"+instance.Host+"/oauth/token", "application/json", bytes.NewBuffer(jsonData)) + + if err != nil { + return nil, err + } + + responseData, err := ioutil.ReadAll(response.Body) + + if err != nil { + return nil, err + } + + authToken := &AuthToken{} + + err = json.Unmarshal(responseData, authToken) + + if err != nil { + return nil, err + } + + if authToken.Error != "" { + return nil, errors.New(authToken.Error + ": " + authToken.ErrorDescription) + } + + return authToken, nil +} + +func (a *App) GetCredentialsForInstanceHost(host string) (*Instance, error) { var instance *Instance err := a.DB.View(func(t *bbolt.Tx) error { - instanceJson := t.Bucket([]byte("instances")).Get([]byte(request.Instance)) + instanceJson := t.Bucket([]byte("instances")).Get([]byte(host)) if instanceJson == nil { instance = nil @@ -90,9 +206,9 @@ func (a *App) GetInstanceDataByRequest(request *AuthRequest) (*Instance, error) }) if instance == nil && err == nil { - (*a.Logger).Infof("No instance data known for host '%s', requesting credentials from host\n", request.Instance) + (*a.Logger).Infof("No instance data known for host '%s', requesting credentials from host\n", host) - instance, err = a.GetCredentialsFromInstanceHost(request.Instance) + instance, err = a.GetCredentialsFromInstanceHost(host) if err != nil { return nil, err @@ -105,18 +221,16 @@ func (a *App) GetInstanceDataByRequest(request *AuthRequest) (*Instance, error) return err } - err = t.Bucket([]byte("instances")).Put([]byte(request.Instance), instanceJson) + err = t.Bucket([]byte("instances")).Put([]byte(host), instanceJson) return err }) - - return instance, err } - return nil, err + return instance, err } -func (a *App) authRequestRedirect(c echo.Context) error { +func (a *App) AuthRequestRedirect(c echo.Context) error { requestId := c.Param("request_id") authRequest := a.AuthMap.GetRequestByID(requestId) @@ -125,10 +239,14 @@ func (a *App) authRequestRedirect(c echo.Context) error { return c.String(http.StatusNotFound, "No authentication request found by this ID") } - instance, err := a.GetInstanceDataByRequest(authRequest) + instance, err := a.GetCredentialsForInstanceHost(authRequest.Instance) if err != nil { - (*a.Logger).Error(err) + (*a.Logger).Errorf( + "Failed to get credentials for instance host %s: %s", + authRequest.Instance, + err, + ) } if instance == nil { @@ -143,6 +261,7 @@ func (a *App) authRequestRedirect(c echo.Context) error { "redirect_uri": []string{a.Config.AppScheme + "://" + a.Config.AppHost + "/confirm"}, "scopes": []string{strings.Join(a.Config.AppScopes, " ")}, "response_type": []string{"code"}, + "state": []string{authRequest.ID}, } return c.Redirect( @@ -237,8 +356,9 @@ func main() { log.Fatal(err) } - e.GET("/ws", a.authRequestWebSocket) - e.GET("/auth/:request_id", a.authRequestRedirect) + e.GET("/ws", a.AuthRequestWebSocket) + e.GET("/auth/:request_id", a.AuthRequestRedirect) + e.GET("/confirm", a.AuthRequestConfirm) e.Logger.Fatal(e.Start(":1323")) } @@ -268,7 +388,9 @@ func (a *App) GetCredentialsFromInstanceHost(host string) (*Instance, error) { return nil, err } - instance := &Instance{} + instance := &Instance{ + Host: host, + } err = json.Unmarshal(body, instance)