Skip to content

Commit

Permalink
Merge pull request #25 from myrunes/dev
Browse files Browse the repository at this point in the history
main 1.9
  • Loading branch information
zekroTJA authored Sep 21, 2020
2 parents efd1d6f + 6eddf2a commit 119e225
Show file tree
Hide file tree
Showing 10 changed files with 276 additions and 81 deletions.
58 changes: 58 additions & 0 deletions docs/restapi-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ The MYRUNES backend provides a RESTful HTTP JSON API providing 100% of the funct
- [**Resources**](#resources)
- [Champions](#champions)
- [Runes and Perks](#runes-and-perks)
- [**Information**](#information)
- [Version](#version)
- [ReCAPTCHA](#recaptcha)
- [**Endpoints**](#endpoints)
- [Users](#users)
- [Get Self User](#get-self-user)
Expand Down Expand Up @@ -343,6 +346,60 @@ The response will contain nested multidimensional arrays of runes available for

---

## Information

### Version

> `GET /api/version`
**Response**

```
HTTP/1.1 200 OK
Cache-Control: max-age=2592000; must-revalidate; proxy-revalidate; public
Content-Type: application/json
Date: Sun, 20 Sep 2020 08:02:45 GMT
Etag: W/"3d17185be51edc954586c4feff03cf31c5d278e6"
Server: MYRUNES v.1.7.1+26
X-Ratelimit-limit: 50
X-Ratelimit-remaining: 49
X-Ratelimit-reset: 0
Content-Length: 60
```
```json
{
"apiversion":"1.8.0",
"release":"TRUE",
"version":"1.7.1+26"
}
```

### Recaptcha

> `GET /api/recaptchainfo`
**Response**

```
HTTP/1.1 200 OK
Cache-Control: max-age=2592000; must-revalidate; proxy-revalidate; public
Content-Type: application/json
Date: Sun, 20 Sep 2020 08:02:45 GMT
Etag: W/"0f3279816a04027124c7d6eaca3b967e15c2d166"
Server: MYRUNES v.1.7.1+26
X-Ratelimit-limit: 50
X-Ratelimit-remaining: 49
X-Ratelimit-reset: 0
Content-Length: 54
```
```json
{
"sitekey":"6Le6IM4ZAAAAAL8iQ0akcye5Sw4I5JbBqRMyD0J8"
}
```

---

## Endpoints

### Authentication
Expand Down Expand Up @@ -473,6 +530,7 @@ X-Ratelimit-Reset: 0
|------|------|-----|---------|-------------|
| `username` | string | Body | | The username of the account |
| `password` | string | Body | | The password of the given user |
| `recaptcharesponse` | string | Body | | ReCAPTCHA verification response token. |
| *`remember`* | boolean | Body | `false` | Sessions defaultly expire after 2 hours. Setting this to true, this duration will be expanded to 30 days. |

**Response**
Expand Down
20 changes: 12 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,27 @@ module github.com/myrunes/backend
go 1.14

require (
github.com/alexedwards/argon2id v0.0.0-20200420065805-90c52fcb498a
github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b
github.com/aws/aws-sdk-go v1.34.27 // indirect
github.com/bwmarrin/snowflake v0.3.0
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/ghodss/yaml v1.0.0
github.com/go-ini/ini v1.60.2 // indirect
github.com/go-ozzo/ozzo-routing v2.1.4+incompatible // indirect
github.com/go-ini/ini v1.61.0 // indirect
github.com/go-redis/redis v6.15.9+incompatible
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/klauspost/compress v1.11.0 // indirect
github.com/minio/minio-go v6.0.14+incompatible
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/qiangxue/fasthttp-routing v0.0.0-20160225050629-6ccdc2a18d87
github.com/valyala/fasthttp v1.15.1
github.com/valyala/fasthttp v1.16.0
github.com/zekroTJA/ratelimit v0.0.0-20190321090824-219ca33049a5
github.com/zekroTJA/shinpuru v0.0.0-20200829092918-bf62c98441a9
github.com/zekroTJA/shinpuru v0.0.0-20200916163845-50e81cb0f86f
github.com/zekroTJA/timedmap v1.2.0
go.mongodb.org/mongo-driver v1.3.3
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
go.mongodb.org/mongo-driver v1.4.1
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
)
90 changes: 65 additions & 25 deletions go.sum

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion internal/static/static.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ const (
DiscordUserAgentPingHeaderVal = "Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com)"

// Current API specification version.
APIVersion = "1.7.0"
APIVersion = "1.8.0"
)
2 changes: 2 additions & 0 deletions internal/webserver/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ var (
// loginRequests describes the request
// model of the login endpoint
type loginRequest struct {
reCaptchaResponse

UserName string `json:"username"`
Password string `json:"password"`
Remember bool `json:"remember"`
Expand Down
60 changes: 27 additions & 33 deletions internal/webserver/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ func (ws *WebServer) handlerCreateUser(ctx *routing.Context) error {
return jsonError(ctx, err, fasthttp.StatusBadRequest)
}

if ok, err := ws.validateReCaptcha(ctx, &data.reCaptchaResponse); !ok {
return err
}

if data.UserName == "" || data.Password == "" || len(data.Password) < 8 {
return jsonError(ctx, errInvalidArguments, fasthttp.StatusBadRequest)
}
Expand Down Expand Up @@ -271,6 +275,14 @@ func (ws *WebServer) handlerPostMail(ctx *routing.Context) error {
return jsonResponse(ctx, nil, fasthttp.StatusOK)
}

recUser, err := ws.db.GetUser(-1, mail.MailAddress)
if err != nil {
return jsonError(ctx, err, fasthttp.StatusInternalServerError)
}
if recUser != nil && recUser.UID != user.UID {
return jsonError(ctx, errEmailAlreadyTaken, fasthttp.StatusBadRequest)
}

const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
token, err := random.String(16, charset)
if err != nil {
Expand Down Expand Up @@ -374,6 +386,10 @@ func (ws *WebServer) handlerPostPwResetConfirm(ctx *routing.Context) error {
return jsonError(ctx, fmt.Errorf("invalid token"), fasthttp.StatusBadRequest)
}

if ok, err := ws.validateReCaptcha(ctx, &data.reCaptchaResponse); !ok {
return err
}

uID, ok := ws.pwReset.GetValue(data.Token).(snowflake.ID)
if !ok {
return jsonError(ctx, fmt.Errorf("wrong data struct in timedmap"), fasthttp.StatusInternalServerError)
Expand All @@ -388,39 +404,6 @@ func (ws *WebServer) handlerPostPwResetConfirm(ctx *routing.Context) error {
return jsonError(ctx, fmt.Errorf("unknown user"), fasthttp.StatusBadRequest)
}

errCheckFailed := fmt.Errorf("security check failed")
if len(data.PageNames) < 3 || data.PageNames[0] == "" || data.PageNames[1] == "" || data.PageNames[2] == "" {
return jsonError(ctx, errCheckFailed, fasthttp.StatusBadRequest)
}

pages, err := ws.db.GetPages(uID, "", "", nil)
if err != nil {
return jsonError(ctx, err, fasthttp.StatusInternalServerError)
}

checkMap := make(map[string]interface{})
for _, guess := range data.PageNames {
if _, ok := checkMap[guess]; ok {
return jsonError(ctx, errCheckFailed, fasthttp.StatusBadRequest)
}
checkMap[guess] = nil
}

var guessed int

for _, page := range pages {
for i, guess := range data.PageNames {
if checkPageName(page.Title, guess, 0.2) {
guessed++
data.PageNames[i] = ""
}
}
}

if guessed < 3 {
return jsonError(ctx, errCheckFailed, fasthttp.StatusBadRequest)
}

ws.pwReset.Remove(data.Token)

var passStr string
Expand Down Expand Up @@ -640,6 +623,17 @@ func (ws *WebServer) handlerGetVersion(ctx *routing.Context) error {
}, fasthttp.StatusOK)
}

// GET /recaptchainfo
func (ws *WebServer) handlerGetReCaptchaInfo(ctx *routing.Context) error {
if ws.config.ReCaptcha == nil || ws.config.ReCaptcha.SiteKey == "" {
return jsonError(ctx, errors.New("not configured"), fasthttp.StatusBadRequest)
}

return jsonCachableResponse(ctx, map[string]string{
"sitekey": ws.config.ReCaptcha.SiteKey,
}, fasthttp.StatusOK)
}

// -----------------------------------------------------
// --- FAVORITES ---

Expand Down
19 changes: 19 additions & 0 deletions internal/webserver/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"

"github.com/myrunes/backend/internal/static"
"github.com/myrunes/backend/pkg/recapatcha"

routing "github.com/qiangxue/fasthttp-routing"
"github.com/valyala/fasthttp"
Expand Down Expand Up @@ -143,6 +144,24 @@ func (ws *WebServer) addHeaders(ctx *routing.Context) error {
return nil
}

func (ws *WebServer) validateReCaptcha(ctx *routing.Context, rcr *reCaptchaResponse) (bool, error) {
if rcr.ReCaptchaResponse == "" {
return false, jsonError(ctx, errMissingReCaptchaResponse, fasthttp.StatusBadRequest)
}

rcRes, err := recapatcha.Validate(ws.config.ReCaptcha.SecretKey, rcr.ReCaptchaResponse)
if err != nil {
return false, jsonError(ctx, err, fasthttp.StatusInternalServerError)
}
if !rcRes.Success {
return false, jsonError(ctx,
fmt.Errorf("recaptcha challenge failed: %+v", rcRes.ErrorCodes),
fasthttp.StatusBadRequest)
}

return true, nil
}

// checkPageName takes an actual pageName, a guess and
// a float value for tollerance between 0 and 1.
// Both, the pageName and guess will be lowercased and
Expand Down
13 changes: 10 additions & 3 deletions internal/webserver/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@ type passwordReset struct {
// verification, the new password string and
// the page name guesses.
type confirmPasswordReset struct {
Token string `json:"token"`
NewPassword string `json:"new_password"`
PageNames []string `json:"page_names"`
reCaptchaResponse

Token string `json:"token"`
NewPassword string `json:"new_password"`
}

// mailConfirmtationData wraps the mail address
Expand All @@ -98,3 +99,9 @@ type mailConfirmationData struct {
UserID snowflake.ID
MailAddress string
}

// reCaptchaResponse wraps a ReCAPTCHA response
// token for ReCAPTCHA validation.
type reCaptchaResponse struct {
ReCaptchaResponse string `json:"recaptcharesponse"`
}
33 changes: 22 additions & 11 deletions internal/webserver/websrever.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,41 @@ import (

// Error Objects
var (
errNotFound = errors.New("not found")
errInvalidArguments = errors.New("invalid arguments")
errUNameInUse = errors.New("user name already in use")
errNoAccess = errors.New("access denied")
errNotFound = errors.New("not found")
errInvalidArguments = errors.New("invalid arguments")
errUNameInUse = errors.New("user name already in use")
errNoAccess = errors.New("access denied")
errMissingReCaptchaResponse = errors.New("missing recaptcha challenge response")
errEmailAlreadyTaken = errors.New("e-mail address is already taken by another account")
)

// Config wraps properties for the
// HTTP REST API server.
type Config struct {
Addr string `json:"addr"`
PathPrefix string `json:"pathprefix"`
TLS *TLSConfig `json:"tls"`
PublicAddr string `json:"publicaddress"`
EnableCors bool `json:"enablecors"`
JWTKey string `json:"jwtkey"`
Addr string `json:"addr"`
PathPrefix string `json:"pathprefix"`
TLS *TLSConfig `json:"tls"`
ReCaptcha *ReCaptchaConfig `json:"recaptcha"`
PublicAddr string `json:"publicaddress"`
EnableCors bool `json:"enablecors"`
JWTKey string `json:"jwtkey"`
}

// TLSCOnfig wraps properties for
// TLSConfig wraps properties for
// TLS encryption.
type TLSConfig struct {
Enabled bool `json:"enabled"`
Cert string `json:"certfile"`
Key string `json:"keyfile"`
}

// ReCaptchaConfig wraps key and secret
// for ReCAPTCHA v2.
type ReCaptchaConfig struct {
SiteKey string `json:"sitekey"`
SecretKey string `json:"secretkey"`
}

// WebServer provices a HTTP REST
// API router.
type WebServer struct {
Expand Down Expand Up @@ -114,6 +124,7 @@ func (ws *WebServer) registerHandlers() {
Post("/logout", ws.auth.CheckRequestAuth, ws.auth.Logout)

api.Get("/version", ws.handlerGetVersion)
api.Get("/recaptchainfo", ws.handlerGetReCaptchaInfo)

assets := api.Group("/assets")
assets.
Expand Down
Loading

0 comments on commit 119e225

Please sign in to comment.