Skip to content
This repository has been archived by the owner on Dec 7, 2023. It is now read-only.

Commit

Permalink
Merge pull request #135 from HackIllinois/staging
Browse files Browse the repository at this point in the history
Staging
  • Loading branch information
ASankaran authored Jan 10, 2019
2 parents 987aab1 + 1a52f7f commit 4456b55
Show file tree
Hide file tree
Showing 71 changed files with 2,212 additions and 200 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ SUBDIRS := $(BASE_DIR)/services/. $(BASE_DIR)/gateway/. $(BASE_DIR)/common/. $(B
TARGETS := all test
SUBDIRS_TARGETS := $(foreach target,$(TARGETS),$(addsuffix $(target),$(SUBDIRS)))
DEPLOY_GATEWAY_TARGETS := gateway
DEPLOY_SERVICE_TARGETS := auth user registration decision rsvp checkin upload mail event stat
DEPLOY_SERVICE_TARGETS := auth user registration decision rsvp checkin upload mail event stat notifications
DEPLOY_TARGETS := $(DEPLOY_GATEWAY_TARGETS) $(DEPLOY_SERVICE_TARGETS)

.PHONY: $(TARGETS) $(SUBDIRS_TARGETS)
Expand Down
9 changes: 9 additions & 0 deletions common/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
)

var IS_PRODUCTION bool
var DEBUG_MODE bool

func init() {
cfg_loader, err := configloader.Load(os.Getenv("HI_CONFIG"))
Expand All @@ -21,4 +22,12 @@ func init() {
}

IS_PRODUCTION = (production == "true")

debug_mode, err := cfg_loader.Get("DEBUG_MODE")

if err != nil {
panic(err)
}

DEBUG_MODE = (debug_mode == "true")
}
9 changes: 9 additions & 0 deletions common/errors/AttributeMismatchError.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package errors

import "net/http"

// Used when the user attempts to perform an action that is not permitted as part of the flow.
// E.g. Attempting to check-in without having RSVPed.
func AttributeMismatchError(raw_error string, message string) ApiError {
return ApiError{Status: http.StatusUnprocessableEntity, Type: "ATTRIBUTE_MISMATCH_ERROR", Message: message, RawError: raw_error}
}
8 changes: 8 additions & 0 deletions common/errors/AuthorizationError.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package errors

import "net/http"

// An error that occurs when an incoming request comes without a JWT token in the Authorization header.
func AuthorizationError(raw_error string, message string) ApiError {
return ApiError{Status: http.StatusForbidden, Type: "AUTHORIZATION_ERROR", Message: message, RawError: raw_error}
}
8 changes: 8 additions & 0 deletions common/errors/DatabaseError.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package errors

import "net/http"

// An error that occurs when a database operation (e.g. fetch / insert / update) doesn't work.
func DatabaseError(raw_error string, message string) ApiError {
return ApiError{Status: http.StatusInternalServerError, Type: "DATABASE_ERROR", Message: message, RawError: raw_error}
}
9 changes: 9 additions & 0 deletions common/errors/InternalError.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package errors

import "net/http"

// Represents errors in the system, including failures in inter-service API calls.
// If there are multiple possible sources of an error, we use this error.
func InternalError(raw_error string, message string) ApiError {
return ApiError{Status: http.StatusInternalServerError, Type: "INTERNAL_ERROR", Message: message, RawError: raw_error}
}
8 changes: 8 additions & 0 deletions common/errors/MalformedRequestError.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package errors

import "net/http"

// An error for when struct validation fails, or there are other issues with the payload to an endpoint.
func MalformedRequestError(raw_error string, message string) ApiError {
return ApiError{Status: http.StatusUnprocessableEntity, Type: "MALFORMED_REQUEST_ERROR", Message: message, RawError: raw_error}
}
5 changes: 0 additions & 5 deletions common/errors/UnauthorizedError.go

This file was deleted.

8 changes: 8 additions & 0 deletions common/errors/UnknownError.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package errors

import "net/http"

// Represents errors in the system whose cause is unidentified.
func UnknownError(raw_error string, message string) ApiError {
return ApiError{Status: http.StatusInternalServerError, Type: "UNKNOWN_ERROR", Message: message, RawError: raw_error}
}
5 changes: 0 additions & 5 deletions common/errors/UnprocessableError.go

This file was deleted.

17 changes: 13 additions & 4 deletions common/errors/errors.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package errors

type APIError struct {
Status int `json:"status,omitempty"`
Title string `json:"title,omitempty"`
Message string `json:"message,omitempty"`
/**
* Status - the HTTP error code to be sent to the client - should be set by constructor
* Type - the broad category - e.g. DatabaseError, AuthorizationError, InternalError
* Message - provides additional details on the specific error that occurred.
* RawError - the raw error (stringified) that caused the panic. It is only included in the response
* to the client, if the config variable DEBUG_MODE is set to true. In other cases, the
* field is set to the empty string, which causes its omission when encoded to JSON.
**/
type ApiError struct {
Status int `json:"status"`
Type string `json:"type"`
Message string `json:"message"`
RawError string `json:"raw_error,omitempty"`
}
41 changes: 30 additions & 11 deletions common/middleware/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package middleware
import (
"encoding/json"
"fmt"
"github.com/HackIllinois/api/common/config"
"github.com/HackIllinois/api/common/errors"
"net/http"
)
Expand All @@ -18,7 +19,7 @@ func LogError(id string, error_message interface{}) {
Error: error_message,
}

error_log_message, err := json.Marshal(log_entry)
error_log_message, err := json.MarshalIndent(log_entry, "", " ")

if err != nil {
fmt.Printf("Failed to marshal error for id: %v\n", id)
Expand All @@ -31,21 +32,39 @@ func LogError(id string, error_message interface{}) {
func ErrorMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
if panic_object := recover(); panic_object != nil {
w.Header().Set("Content-Type", "application/json")
switch panic_error := rec.(type) {
case errors.APIError:
LogError(r.Header.Get("HackIllinois-Identity"), panic_error)
w.WriteHeader(panic_error.Status)
json.NewEncoder(w).Encode(panic_error)
switch panic_error := panic_object.(type) {
case errors.ApiError:
handleApiError(panic_error, w, r)
default:
LogError(r.Header.Get("HackIllinois-Identity"), rec)
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(errors.APIError{Status: 500, Message: "Unknown Error"})
handleUnknownError(panic_error, w, r)
}
}
}()

next.ServeHTTP(w, r)
})
}

func handleApiError(err errors.ApiError, w http.ResponseWriter, r *http.Request) {
LogError(r.Header.Get("HackIllinois-Identity"), err)

w.WriteHeader(err.Status)

// Strip the raw error string if we're not in debug mode
if config.DEBUG_MODE {
err.RawError = ""
}

json.NewEncoder(w).Encode(err)
}

func handleUnknownError(err interface{}, w http.ResponseWriter, r *http.Request) {
LogError(r.Header.Get("HackIllinois-Identity"), err)

w.WriteHeader(http.StatusInternalServerError)

err_string := fmt.Sprintf("%v", err)

json.NewEncoder(w).Encode(errors.UnknownError(err_string, err_string))
}
11 changes: 11 additions & 0 deletions config/dev_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"MAIL_SERVICE": "http://localhost:8009",
"EVENT_SERVICE": "http://localhost:8010",
"STAT_SERVICE": "http://localhost:8011",
"NOTIFICATIONS_SERVICE": "http://localhost:8012",

"GATEWAY_PORT": "8000",
"AUTH_PORT": ":8002",
Expand All @@ -21,6 +22,7 @@
"MAIL_PORT": ":8009",
"EVENT_PORT": ":8010",
"STAT_PORT": ":8011",
"NOTIFICATIONS_PORT": ":8012",

"AUTH_DB_HOST": "localhost",
"USER_DB_HOST": "localhost",
Expand All @@ -32,6 +34,7 @@
"MAIL_DB_HOST": "localhost",
"EVENT_DB_HOST": "localhost",
"STAT_DB_HOST": "localhost",
"NOTIFICATIONS_DB_HOST": "localhost",

"AUTH_DB_NAME": "auth",
"USER_DB_NAME": "user",
Expand All @@ -43,10 +46,16 @@
"MAIL_DB_NAME": "mail",
"EVENT_DB_NAME": "event",
"STAT_DB_NAME": "stat",
"NOTIFICATIONS_DB_NAME": "notifications",

"S3_REGION": "us-east-1",
"S3_BUCKET": "hackillinois-upload-2019",

"SNS_REGION": "us-east-1",

"ANDROID_PLATFORM_ARN": "",
"IOS_PLATFORM_ARN": "",

"STAFF_DOMAIN": "hackillinois.org",
"SYSTEM_ADMIN_EMAIL": "[email protected]",

Expand All @@ -65,6 +74,8 @@

"IS_PRODUCTION": "false",

"DEBUG_MODE": "true",

"DECISION_EXPIRATION_HOURS": "48",

"STAT_ENDPOINTS": {
Expand Down
6 changes: 6 additions & 0 deletions config/production_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"MAIL_PORT": ":8009",
"EVENT_PORT": ":8010",
"STAT_PORT": ":8011",
"NOTIFICATIONS_PORT": ":8012",

"AUTH_DB_NAME": "auth",
"USER_DB_NAME": "user",
Expand All @@ -32,10 +33,13 @@
"MAIL_DB_NAME": "mail",
"EVENT_DB_NAME": "event",
"STAT_DB_NAME": "stat",
"NOTIFICATIONS_DB_NAME": "notifications",

"S3_REGION": "us-east-1",
"S3_BUCKET": "hackillinois-upload-2019",

"SNS_REGION": "us-east-1",

"STAFF_DOMAIN": "hackillinois.org",
"SYSTEM_ADMIN_EMAIL": "[email protected]",

Expand All @@ -44,6 +48,8 @@

"IS_PRODUCTION": "true",

"DEBUG_MODE": "false",

"DECISION_EXPIRATION_HOURS": "48",

"STAT_ENDPOINTS": {
Expand Down
10 changes: 10 additions & 0 deletions config/test_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"MAIL_PORT": ":8009",
"EVENT_PORT": ":8010",
"STAT_PORT": ":8011",
"NOTIFICATIONS_PORT": ":8012",

"AUTH_DB_HOST": "localhost",
"USER_DB_HOST": "localhost",
Expand All @@ -32,6 +33,7 @@
"MAIL_DB_HOST": "localhost",
"EVENT_DB_HOST": "localhost",
"STAT_DB_HOST": "localhost",
"NOTIFICATIONS_DB_HOST": "localhost",

"AUTH_DB_NAME": "test-auth",
"USER_DB_NAME": "test-user",
Expand All @@ -43,10 +45,16 @@
"MAIL_DB_NAME": "test-mail",
"EVENT_DB_NAME": "test-event",
"STAT_DB_NAME": "test-stat",
"NOTIFICATIONS_DB_NAME": "test-notifications",

"S3_REGION": "us-east-1",
"S3_BUCKET": "hackillinois-upload-2019",

"SNS_REGION": "us-east-1",

"ANDROID_PLATFORM_ARN": "",
"IOS_PLATFORM_ARN": "",

"STAFF_DOMAIN": "hackillinois.org",
"SYSTEM_ADMIN_EMAIL": "[email protected]",

Expand All @@ -65,6 +73,8 @@

"IS_PRODUCTION": "false",

"DEBUG_MODE": "true",

"DECISION_EXPIRATION_HOURS": "48",

"STAT_ENDPOINTS": {
Expand Down
22 changes: 20 additions & 2 deletions documentation/docs/reference/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,26 @@ Each microservice is responsible for doing only one set of tasks. For example, o

For authorization we use [JSON Web Tokens (JWTs)](https://jwt.io) that encode a user ID and some more information, in a system similar to [Bearer (or token-based) authentication](https://swagger.io/docs/specification/authentication/bearer-authentication/).

When a client makes an HTTP request to `api.hackillinois.org`, it is taken through several middleware. One of them is the *Authentication* middleware, which ensure the user is authenticated. Another one is the *Identification* middleware, and puts the user ID of the requesting user in the HackIllinois-Identity header, which can be used by the individual services.
When a client makes an HTTP request to `api.hackillinois.org`, it is taken through several middleware. One of them is the *Authentication* middleware, which ensures the user is authenticated. Another one is the *Identification* middleware, and puts the user ID of the requesting user in the HackIllinois-Identity header, which can be used by the individual services. The *Error* middleware allows passing of errors to the client using standard HTTP mechanisms, such as status codes, and response bodies.

The authorized request is then forwarded to the relevant micro-service based on routes configured in the [gateway](/reference/gateway), where controllers present in each micro-service process the request, call various service funcitons, perform the action requested, and return the response, which is passed back to the user, via Arbor.

Our persistence layer consists of a [MongoDB](https://mongodb.com) database, which has collections storing data relevant to each service.
Our persistence layer consists of a [MongoDB](https://mongodb.com) database, which has collections storing data relevant to each service.

## Errors

Setting the DEBUG_MODE to "true" in the config file allows raw error messages (if applicable) to be passed through to the client. Otherwise, the raw error is suppressed.

Errors are classified into the following types:

1. **DatabaseError** - When database operations, such as fetch / insert / update) doesn't work. These are usually returned when a document / record that was requested wasn't found, such as when an operation is performed on an inexistent user.

2. **MalformedRequestError** - When the request is invalid or missing some key information. Possible scenarios are, when field validation fails on a request body, or when an ID is missing for an endpoint that depends on it.

3. **AuthorizationError** - When an authentication / authorization attempt fails. Possible scenarios include when OAuth-related services fail, such as when an authorization code is incorrect, a token is invalid / has expired etc.

4. **AttributeMismatchError** - When an action is performed on a user who is missing some attribute, such as when a check-in (without override) is attempted for a user who doesn't have a registration or RSVP, modifying a decision on a candidate (hacker) whose decision has already been finalized by a senior staff member etc.

5. **InternalError** - When there could be multiple possible causes of the error, this is what we use. Using DEBUG_MODE to get the raw error is highly recommended to expedite bug resolution.

6. **UnknownError** - When the cause of an error cannot be identified.
6 changes: 4 additions & 2 deletions documentation/docs/reference/services/Authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Response format:
}
```
GET /auth/roles/USERID/
--------------------------
-----------------------

Gets the roles of the user with the id `USERID`.

Expand All @@ -50,6 +50,7 @@ Response format:
```

PUT /auth/roles/add/
--------------------

Adds the given `role` to the user with the given `id`. The updated user's roles will be returned.

Expand All @@ -74,6 +75,7 @@ Response format:
```

PUT /auth/roles/remove/
-----------------------

Removes the given `role` from the user with the given `id`. The updated user's roles will be returned.

Expand All @@ -96,7 +98,7 @@ Response format:
```

GET /auth/token/refresh/
-----------------
------------------------

Creates a new JWT for the current user. This is useful when the user's roles change, and the updated roles need to be encoded into a new JWT, such as during registration.

Expand Down
Loading

0 comments on commit 4456b55

Please sign in to comment.