finish login story

This commit is contained in:
41666 2020-12-01 23:13:32 -05:00
parent a23184efd2
commit c9cb4c95bc
34 changed files with 14564 additions and 21666 deletions

4
.babelrc Normal file
View file

@ -0,0 +1,4 @@
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
}

24
.env.example Normal file
View file

@ -0,0 +1,24 @@
# Make an application at https://discord.com/developers/applications
BOT_CLIENT_ID=000000000000000000
BOT_CLIENT_SECRET=RnX8pXXXXXXXXXXXXXXXXXXXXXXXXXu-
BOT_TOKEN=Mzk2MjI3MTM0MjI3NXXXXXXXXXXXXXXXXXXXXXPUlYoARXXXXXXXXXXXXXX
# If 6600 or 6601 is taken, change this, and all other 6600/6601 references.
PORT=6600
UI_PORT=6601
# This is probably right, unless you set a different port.
OAUTH_REDIRECT_URI=http://localhost:6600/login-handler
# Again, probably right. Do not put a trailing /
UI_PUBLIC_URI=http://localhost:6601
API_PUBLIC_URI=http://localhost:6600
# Google Cloud Project ID -- This only needs to be set for production/public cloud
GCP_PROJECT_ID=roleypoly-0000000
# Unset this if you're planning to use public cloud Firestore as opposed to local
# Or set this differently if you changed your local endpoint port.
FIRESTORE_EMULATOR_HOST=localhost:6691
REDIS_ENDPOINT=localhost:6692

View file

@ -34,7 +34,6 @@ module.exports = {
},
},
],
'@typescript-eslint/naming-convention': 'error',
'@typescript-eslint/no-empty-function': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-new': 'error',

2
.gitignore vendored
View file

@ -9,3 +9,5 @@ docker-compose.yaml
*.log
storybook-static
dist
.firebaserc
.next

View file

@ -1,3 +1,4 @@
bazel-*
dist
storybook-static
storybook-static
.next

3
go.mod
View file

@ -3,9 +3,11 @@ module github.com/roleypoly/roleypoly
go 1.15
require (
cloud.google.com/go/firestore v1.3.0
github.com/GoogleCloudPlatform/functions-framework-go v1.2.0
github.com/bwmarrin/discordgo v0.22.0
github.com/dghubble/trie v0.0.0-20201011220304-ed6d6b8add55
github.com/google/martian v2.1.0+incompatible
github.com/gorilla/websocket v1.4.2 // indirect
github.com/joho/godotenv v1.3.0
github.com/lampjaw/discordclient v0.0.0-20200923011548-6558fc9e89df
@ -13,5 +15,6 @@ require (
github.com/segmentio/ksuid v1.0.3
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 // indirect
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
google.golang.org/api v0.30.0
k8s.io/klog v1.0.0
)

14
go.sum
View file

@ -11,9 +11,11 @@ cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.61.0/go.mod h1:XukKJg4Y7QsUu0Hxg3qQKUWR4VuWivmyMK2+rUyxAqw=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.63.0 h1:A+DfAZQ/eWca7gvu42CS6FNSDX4R8cghF+XfWLn4R6g=
cloud.google.com/go v0.63.0/go.mod h1:GmezbQc7T2snqkEXWfZ0sy0VfkB/ivI2DdtJL2DEmlg=
cloud.google.com/go v0.72.0 h1:eWRCuwubtDrCJG0oSUMgnsbD4CmPFQF2ei4OFbXvwww=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@ -22,6 +24,8 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.3.0 h1:QaBSisuvNi9/o+3nCHqUEfduHCPfhEw2jcUofi0n8oY=
cloud.google.com/go/firestore v1.3.0/go.mod h1:Qt0gS9Qz9tROrmgFavo36+hdST1FXvmtnGnO0Dr03pU=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@ -98,6 +102,7 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -111,6 +116,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
@ -242,6 +248,7 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -327,6 +334,8 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200727233628-55644ead90ce/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
@ -350,6 +359,7 @@ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -383,8 +393,11 @@ google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200728010541-3dc8dca74b7b/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 h1:LCO0fg4kb6WwkXQXRQQgUYsFeFb5taTX5WAx5O/Vt28=
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@ -397,6 +410,7 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=

View file

@ -9,16 +9,21 @@ import (
"github.com/GoogleCloudPlatform/functions-framework-go/funcframework"
_ "github.com/joho/godotenv/autoload"
"k8s.io/klog"
botjoin "github.com/roleypoly/roleypoly/src/functions/bot-join"
createsession "github.com/roleypoly/roleypoly/src/functions/create-session"
loginbounce "github.com/roleypoly/roleypoly/src/functions/login-bounce"
loginhandler "github.com/roleypoly/roleypoly/src/functions/login-handler"
sessiondata "github.com/roleypoly/roleypoly/src/functions/session-data"
sessionprewarm "github.com/roleypoly/roleypoly/src/functions/session-prewarm"
)
var mappings map[string]http.HandlerFunc = map[string]http.HandlerFunc{
"/session-prewarm": sessionprewarm.SessionPrewarm,
"/session-data": sessiondata.SessionData,
"/bot-join": botjoin.BotJoin,
"/session-data": sessiondata.SessionData,
"/bot-join": botjoin.BotJoin,
"/login-bounce": loginbounce.LoginBounce,
"/login-handler": loginhandler.LoginHandler,
"/create-session": createsession.CreateSession,
}
var port string
@ -32,7 +37,7 @@ func main() {
}
for path, handler := range mappings {
err := funcframework.RegisterHTTPFunctionContext(ctx, path, handler)
err := funcframework.RegisterHTTPFunctionContext(ctx, path, wrapLogging(handler))
if err != nil {
log.Fatalf("funcframework.RegisterHTTPFunctionContext: %v\n", err)
}
@ -62,3 +67,10 @@ func rootHandler(rw http.ResponseWriter, r *http.Request) {
fmt.Fprintln(rw, body)
}
func wrapLogging(fn func(rw http.ResponseWriter, r *http.Request)) func(rw http.ResponseWriter, r *http.Request) {
return func(rw http.ResponseWriter, r *http.Request) {
klog.Info(r.Method, " ", r.RequestURI)
fn(rw, r)
}
}

2
next-env.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />

21586
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -20,40 +20,44 @@
"now-build": "run-s storybook:build",
"storybook": "start-storybook -p 6006",
"storybook:build": "build-storybook",
"test": "jest"
"test": "jest",
"ui:build": "next build",
"ui:dev": "next dev -p 6601"
},
"dependencies": {
"chroma-js": "^2.1.0",
"next": "^10.0.2",
"next": "^10.0.3",
"react": "^17.0.1",
"react-custom-scrollbars": "^4.2.1",
"react-dom": "^17.0.1",
"react-icons": "^3.11.0",
"react-icons": "^4.1.0",
"react-is": "^17.0.1",
"react-tooltip": "^4.2.11",
"styled-components": "^5.2.1"
},
"devDependencies": {
"@babel/core": "^7.12.7",
"@storybook/addon-actions": "^6.1.2",
"@storybook/addon-essentials": "^6.1.2",
"@storybook/addon-links": "^6.1.2",
"@storybook/addons": "^6.1.2",
"@storybook/react": "^6.1.2",
"@storybook/theming": "^6.1.2",
"@babel/core": "^7.12.9",
"@storybook/addon-actions": "^6.1.9",
"@storybook/addon-essentials": "^6.1.9",
"@storybook/addon-links": "^6.1.9",
"@storybook/addons": "^6.1.9",
"@storybook/react": "^6.1.9",
"@storybook/theming": "^6.1.9",
"@types/chroma-js": "^2.1.2",
"@types/enzyme": "^3.10.8",
"@types/enzyme-adapter-react-16": "^1.0.6",
"@types/jest": "^26.0.15",
"@types/jest": "^26.0.16",
"@types/minimist": "^1.2.1",
"@types/node": "^14.14.9",
"@types/node": "^14.14.10",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/styled-components": "^5.1.4",
"@typescript-eslint/eslint-plugin": "^4.8.1",
"@typescript-eslint/eslint-plugin-tslint": "^4.8.1",
"@typescript-eslint/parser": "^4.8.1",
"@typescript-eslint/eslint-plugin": "^4.9.0",
"@typescript-eslint/eslint-plugin-tslint": "^4.9.0",
"@typescript-eslint/parser": "^4.9.0",
"babel-jest": "^26.6.3",
"babel-loader": "^8.2.1",
"babel-loader": "^8.2.2",
"babel-plugin-styled-components": "1.12.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"enzyme-to-json": "^3.6.1",
@ -70,10 +74,9 @@
"jest-styled-components": "^7.0.3",
"minimist": "^1.2.5",
"npm-run-all": "^4.1.5",
"prettier": "^2.2.0",
"prettier": "^2.2.1",
"prettier-plugin-packagejson": "^2.2.8",
"prettier-plugin-sh": "^0.6.0",
"react-is": "^17.0.1",
"stylelint": "^13.8.0",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^20.0.0",

View file

@ -43,6 +43,15 @@ func (g GetenvValue) StringSlice(optionalDelimiter ...string) []string {
return strings.Split(g.value, delimiter)
}
// SafeURL removes any trailing slash
func (g GetenvValue) SafeURL() string {
if g.value[len(g.value)-1] == '/' {
return g.value[:len(g.value)-1]
}
return g.value
}
func (g GetenvValue) Bool() bool {
lowercaseValue := strings.ToLower(g.value)
if g.value == "1" || lowercaseValue == "true" || lowercaseValue == "yes" {

View file

@ -2,6 +2,7 @@ package common_test
import (
"os"
"strings"
"testing"
"github.com/onsi/gomega"
@ -14,6 +15,8 @@ var (
"slice": "hello,world",
"slice_no_delim": "hello world",
"slice_set_delim": "hello|world",
"url": "https://google.com",
"url_trailing": "https://google.com/",
"number": "10005",
"number_bad": "abc123",
"bool": "true",
@ -61,6 +64,19 @@ func TestEnvconfigStringSliceSetDelimeter(t *testing.T) {
}
}
func TestEnvconfigSafeURL(t *testing.T) {
testUrl := common.Getenv("test__url").SafeURL()
if strings.HasSuffix(testUrl, "/") {
t.FailNow()
}
}
func TestEnvconfigSafeURLWithTrailing(t *testing.T) {
testUrl := common.Getenv("test__url_trailing").SafeURL()
if strings.HasSuffix(testUrl, "/") {
t.FailNow()
}
}
func TestEnvconfigNumber(t *testing.T) {
testNum := common.Getenv("test__number").Number()
if testNum != 10005 {

View file

@ -62,5 +62,14 @@ func Unstash(rw http.ResponseWriter, req *http.Request, defaultURL string) {
redirectURL = cookie.Value
}
unsetter := http.Cookie{
Name: "rp_stashed_url",
Value: "",
MaxAge: -1,
HttpOnly: true,
}
rw.Header().Set("set-cookie", unsetter.String())
Bounce(rw, redirectURL)
}

View file

@ -0,0 +1,15 @@
package faas
import (
"net/http"
"github.com/roleypoly/roleypoly/src/common/types"
)
func Fingerprint(req *http.Request) types.Fingerprint {
return types.Fingerprint{
UserAgent: req.UserAgent(),
ClientIP: req.RemoteAddr,
ForwardedFor: req.Header.Get("x-forwarded-for"),
}
}

20
src/common/types/User.go Normal file
View file

@ -0,0 +1,20 @@
package types
type DiscordUser struct {
ID string `json:"id,omitempty"`
Username string `json:"username,omitempty"`
Discriminator string `json:"discriminator,omitempty"`
Avatar string `json:"avatar,omitempty"`
Bot bool `json:"bot,omitempty"`
}
type Member struct {
GuildID string `json:"guildid,omitempty"`
Roles []string `json:"rolesList,omitempty"`
Nick string `json:"nick,omitempty"`
User DiscordUser `json:"user,omitempty"`
}
type RoleypolyUser struct {
DiscordUser DiscordUser `json:"discorduser,omitempty"`
}

View file

@ -0,0 +1,31 @@
package types
import "time"
// CreateSessionRequest is the payload to /create-session
type CreateSessionRequest struct {
AccessTokenResponse AccessTokenResponse
Fingerprint Fingerprint
}
type Fingerprint struct {
UserAgent string
ClientIP string
ForwardedFor string
}
type CreateSessionResponse struct {
SessionID string
}
type SessionData struct {
SessionID string
Fingerprint Fingerprint
AccessTokens AccessTokenResponse
UserData UserData
}
type UserData struct {
DataExpires time.Time
UserID string
}

View file

@ -0,0 +1,10 @@
package types
// AccessTokenResponse is the response for Discord's OAuth token grant flow
type AccessTokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
Scope string `json:"scope"`
}

View file

@ -1,5 +1,6 @@
import styled, { createGlobalStyle } from 'styled-components';
import { palette } from 'roleypoly/design-system/atoms/colors';
import { fontCSS } from 'roleypoly/design-system/atoms/fonts';
export const Content = styled.div<{ small?: boolean }>`
margin: 0 auto;
@ -15,6 +16,7 @@ export const GlobalStyles = createGlobalStyle`
color: ${palette.grey600};
overflow-y: hidden;
scroll-behavior: smooth;
${fontCSS}
}
* {
box-sizing: border-box;

View file

@ -1,16 +0,0 @@
import * as React from 'react';
import { organismStories } from 'roleypoly/design-system/organisms/organisms.story';
import { ErrorBanner } from './ErrorBanner';
import { text } from '@storybook/addon-knobs';
const story = organismStories('Error Banner', module);
story.add('Error Banner', () => (
<ErrorBanner
message={{
english: text('English', 'Primary Text'),
japanese: text('Japanese (Subtext)', 'Subtext'),
friendlyCode: text('Friendly Code', 'Oops!'),
}}
/>
));

View file

@ -6,11 +6,13 @@ import { PreauthGreeting } from 'roleypoly/design-system/molecules/preauth-greet
import { PreauthSecretCode } from 'roleypoly/design-system/molecules/preauth-secret-code';
import { Guild } from 'roleypoly/common/types';
import styled from 'styled-components';
import Link from 'next/link';
export type PreauthProps = {
guildSlug?: Guild;
onSendSecretCode: (code: string) => void;
botName?: string;
discordOAuthLink?: string;
};
const Centered = styled.div`
@ -33,16 +35,18 @@ export const Preauth = (props: PreauthProps) => {
<Centered>
{props.guildSlug && <PreauthGreeting guildSlug={props.guildSlug} />}
<WidthContainer>
<Button
color="discord"
icon={
<div style={{ position: 'relative', top: 3 }}>
<FaDiscord />
</div>
}
>
Sign in with Discord
</Button>
<Link href={props.discordOAuthLink || '#'}>
<Button
color="discord"
icon={
<div style={{ position: 'relative', top: 3 }}>
<FaDiscord />
</div>
}
>
Sign in with Discord
</Button>
</Link>
</WidthContainer>
<Space />
<WidthContainer>

View file

@ -1,18 +0,0 @@
import * as React from 'react';
import { templateStories } from 'templates/templates.story';
import { AuthLogin } from './AuthLogin';
import { action } from '@storybook/addon-actions';
import { guild } from 'roleypoly/common/types/storyData';
const story = templateStories('Login', module);
story.add('No Slug', () => (
<AuthLogin botName="roleypoly#3266" onSendSecretCode={action('secret code!')} />
));
story.add('With Slug', () => (
<AuthLogin
botName="roleypoly#3266"
guildSlug={guild}
onSendSecretCode={action('secret code!')}
/>
));

View file

@ -0,0 +1,59 @@
package createsession
import (
"context"
"encoding/json"
"net/http"
"time"
"cloud.google.com/go/firestore"
"k8s.io/klog"
"github.com/roleypoly/roleypoly/src/common"
"github.com/roleypoly/roleypoly/src/common/types"
)
var (
firestoreClient *firestore.Client
projectID = common.Getenv("GCP_PROJECT_ID").String()
firestoreEndpoint = common.Getenv("FIRESTORE_ENDPOINT").String()
)
func init() {
var err error // shadow avoidance
ctx := context.Background()
firestoreClient, err = firestore.NewClient(ctx, projectID)
if err != nil {
panic(err)
}
}
func CreateSession(rw http.ResponseWriter, req *http.Request) {
requestData := types.CreateSessionRequest{}
json.NewDecoder(req.Body).Decode(&requestData)
klog.Info("Creating session...")
sessionData := types.SessionData{
AccessTokens: requestData.AccessTokenResponse,
Fingerprint: requestData.Fingerprint,
UserData: types.UserData{},
}
ctx, ctxCancel := context.WithTimeout(context.Background(), 15*time.Second)
defer ctxCancel()
docRef, _, err := firestoreClient.Collection("sessions").Add(ctx, sessionData)
if err != nil {
rw.WriteHeader(500)
klog.Error("error: create session in firestore: ", err)
}
klog.Info("Created session ", docRef.ID)
responseData := types.CreateSessionResponse{
SessionID: docRef.ID,
}
json.NewEncoder(rw).Encode(responseData)
}

View file

@ -14,7 +14,7 @@ import (
var (
redirectPathTemplate = template.Must(
template.New("redirect").Parse(
`https://discord.com/api/oauth2/authorize?client_id={{.ClientID}}&scope=identify,guilds&redirect_uri={{.RedirectURI}}&state={{.State}}`,
`https://discord.com/api/oauth2/authorize?client_id={{.ClientID}}&response_type=code&scope=identify%20guilds&redirect_uri={{urlquery .RedirectURI}}&state={{.State}}`,
),
)
clientID = common.Getenv("BOT_CLIENT_ID").String()

View file

@ -0,0 +1,76 @@
package loginhandler
import (
"bytes"
"encoding/json"
"net/http"
"net/url"
"time"
"github.com/roleypoly/roleypoly/src/common"
"github.com/roleypoly/roleypoly/src/common/faas"
"github.com/roleypoly/roleypoly/src/common/types"
"github.com/segmentio/ksuid"
"k8s.io/klog"
)
var (
// HTTPClient is an overridable HTTP client with a 15 second timeout
HTTPClient = http.Client{Timeout: 15 * time.Second}
uiPath = common.Getenv("UI_PUBLIC_URI").SafeURL()
apiPath = common.Getenv("API_PUBLIC_URI").SafeURL()
clientID = common.Getenv("BOT_CLIENT_ID").String()
clientSecret = common.Getenv("BOT_CLIENT_SECRET").String()
redirectURI = common.Getenv("OAUTH_REDIRECT_URI").String()
)
func LoginHandler(rw http.ResponseWriter, req *http.Request) {
query := req.URL.Query()
state := query.Get("state")
stateID, err := ksuid.Parse(state)
if err != nil || stateID.IsNil() || stateID.Time().Add(5*time.Minute).Before(time.Now()) {
faas.Bounce(rw, uiPath+"/machinery/error?error_code=auth_failure")
return
}
code := query.Get("code")
body := url.Values{}
body.Set("client_id", clientID)
body.Set("client_secret", clientSecret)
body.Set("grant_type", "authorization_code")
body.Set("code", code)
body.Set("redirect_uri", redirectURI)
body.Set("scope", "identify guilds")
response, err := HTTPClient.PostForm("https://discord.com/api/v8/oauth2/token", body)
if err != nil {
klog.Error("token fetch failed: ", err)
faas.Bounce(rw, uiPath+"/machinery/error?error_code=auth_failure")
return
}
tokens := types.AccessTokenResponse{}
json.NewDecoder(response.Body).Decode(&tokens)
sessionRequest := types.CreateSessionRequest{
AccessTokenResponse: tokens,
Fingerprint: faas.Fingerprint(req),
}
buf := bytes.Buffer{}
json.NewEncoder(&buf).Encode(sessionRequest)
response, err = HTTPClient.Post(apiPath+"/create-session", "application/json", &buf)
if err != nil {
klog.Error("create session failed: ", err)
faas.Bounce(rw, uiPath+"/machinery/error?error_code=auth_failure")
return
}
session := types.CreateSessionResponse{}
json.NewDecoder(response.Body).Decode(&session)
faas.Bounce(rw, uiPath+"/machinery/new-session?session_id="+session.SessionID)
}

View file

@ -1,7 +0,0 @@
package sessionprewarm
import "net/http"
func SessionPrewarm(rw http.ResponseWriter, r *http.Request) {
rw.Write([]byte("hello work!"))
}

11
src/pages/_app.tsx Normal file
View file

@ -0,0 +1,11 @@
import { AppProps } from 'next/app';
import * as React from 'react';
import { InjectTypekitFont } from 'roleypoly/design-system/atoms/fonts';
const App = (props: AppProps) => (
<>
<InjectTypekitFont />
<props.Component {...props.pageProps} />
</>
);
export default App;

30
src/pages/_document.tsx Normal file
View file

@ -0,0 +1,30 @@
import Document, { DocumentContext } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
}

16
src/pages/auth/login.tsx Normal file
View file

@ -0,0 +1,16 @@
import * as React from 'react';
import { AuthLogin } from 'roleypoly/design-system/templates/auth-login';
const loginPage = () => {
const onSendSecretCode = (code: string) => {
console.log(code);
};
return (
<AuthLogin
onSendSecretCode={onSendSecretCode}
discordOAuthLink="http://localhost:6600/login-bounce"
/>
);
};
export default loginPage;

5
src/pages/index.tsx Normal file
View file

@ -0,0 +1,5 @@
import * as React from 'react';
import { LandingTemplate } from 'roleypoly/design-system/templates/landing';
const Index = () => <LandingTemplate />;
export default Index;

View file

@ -0,0 +1,17 @@
import { NextPageContext } from 'next';
import * as React from 'react';
import { Error } from 'roleypoly/design-system/templates/errors';
type Props = {
errorCode: string | number | any;
};
const ErrorPage = (props: Props) => <Error code={props.errorCode} />;
ErrorPage.getInitialProps = (context: NextPageContext): Props => {
return {
errorCode: context.err || context.query.error_code,
};
};
export default ErrorPage;

View file

@ -0,0 +1,27 @@
import { NextPageContext } from 'next';
import * as React from 'react';
type Props = {
sessionID: string;
};
const NewSession = (props: Props) => {
const { sessionID } = props;
React.useEffect(() => {
sessionStorage.setItem('session_key', sessionID);
location.href = '/';
}, [sessionID]);
return <div>Logging you in...</div>;
};
NewSession.getInitialProps = (context: NextPageContext): Props => {
const sessionID = context.query.session_id;
if (!sessionID) {
throw new Error("I shouldn't be here today.");
}
return { sessionID: sessionID as string };
};
export default NewSession;

View file

@ -10,7 +10,7 @@
"es2015.iterable",
"es2015.core"
],
"jsx": "react",
"jsx": "preserve",
"baseUrl": ".",
"strict": true,
"esModuleInterop": true,
@ -20,6 +20,13 @@
"moduleResolution": "node",
"paths": {
"roleypoly/*": ["./src/*"]
}
}
},
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"resolveJsonModule": true,
"isolatedModules": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

14097
yarn.lock Normal file

File diff suppressed because it is too large Load diff