chore(project): bootstrap project tree

This commit is contained in:
2020-08-08 15:04:59 +02:00
parent c11d55b61c
commit 5806f196c4
77 changed files with 14666 additions and 0 deletions

77
internal/route/login.go Normal file
View File

@ -0,0 +1,77 @@
package route
import (
"net/http"
"forge.cadoles.com/Cadoles/guesstimate/internal/model"
"forge.cadoles.com/Cadoles/guesstimate/internal/orm"
"forge.cadoles.com/Cadoles/guesstimate/internal/session"
"github.com/pkg/errors"
"forge.cadoles.com/Cadoles/guesstimate/internal/config"
oidc "forge.cadoles.com/wpetit/goweb-oidc"
"gitlab.com/wpetit/goweb/logger"
"gitlab.com/wpetit/goweb/middleware/container"
)
func handleLogin(w http.ResponseWriter, r *http.Request) {
ctn := container.Must(r.Context())
client := oidc.Must(ctn)
client.Login(w, r)
}
type emailClaims struct {
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
}
func handleLoginCallback(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctn := container.Must(ctx)
conf := config.Must(ctn)
idToken, err := oidc.IDToken(w, r)
if err != nil {
logger.Error(ctx, "could not retrieve idToken", logger.E(err))
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
logger.Info(ctx, "user logged in", logger.F("sub", idToken.Subject))
claims := &emailClaims{}
if err := idToken.Claims(claims); err != nil {
panic(errors.WithStack(err))
}
// TODO implements better UX in case of errors
if claims.Email == "" {
http.Error(w, "an email is expected to access this app", http.StatusForbidden)
return
}
if !claims.EmailVerified {
http.Error(w, "your email must be verified to access this app", http.StatusForbidden)
return
}
db := orm.Must(ctn).DB()
repo := model.NewUserRepository(db)
if _, err := repo.CreateOrConnectUser(ctx, claims.Email); err != nil {
panic(errors.Wrap(err, "could not upsert user"))
}
if err := session.SaveUserEmail(w, r, claims.Email); err != nil {
panic(errors.WithStack(err))
}
http.Redirect(w, r, conf.HTTP.FrontendURL, http.StatusSeeOther)
}

40
internal/route/logout.go Normal file
View File

@ -0,0 +1,40 @@
package route
import (
"net/http"
"forge.cadoles.com/Cadoles/guesstimate/internal/session"
"github.com/pkg/errors"
"forge.cadoles.com/Cadoles/guesstimate/internal/config"
oidc "forge.cadoles.com/wpetit/goweb-oidc"
"gitlab.com/wpetit/goweb/logger"
"gitlab.com/wpetit/goweb/middleware/container"
)
func handleLogout(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctn := container.Must(ctx)
conf := config.Must(ctn)
client := oidc.Must(ctn)
logger.Info(
ctx,
"logging out user",
logger.F("postLogoutURL", conf.OIDC.PostLogoutRedirectURL),
)
if err := session.ClearUserEmail(w, r, false); err != nil {
panic(errors.WithStack(err))
}
client.Logout(w, r, conf.OIDC.PostLogoutRedirectURL)
}
func handleLogoutRedirect(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctn := container.Must(ctx)
conf := config.Must(ctn)
http.Redirect(w, r, conf.HTTP.FrontendURL, http.StatusSeeOther)
}

79
internal/route/mount.go Normal file
View File

@ -0,0 +1,79 @@
package route
import (
"net/http"
"path"
"time"
"forge.cadoles.com/Cadoles/guesstimate/internal/config"
"forge.cadoles.com/Cadoles/guesstimate/internal/graph"
"forge.cadoles.com/Cadoles/guesstimate/internal/graph/generated"
"forge.cadoles.com/Cadoles/guesstimate/internal/session"
oidc "forge.cadoles.com/wpetit/goweb-oidc"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/handler/extension"
"github.com/99designs/gqlgen/graphql/handler/transport"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/gorilla/websocket"
"github.com/go-chi/chi"
"github.com/rs/cors"
"gitlab.com/wpetit/goweb/static"
)
func Mount(r *chi.Mux, config *config.Config) error {
r.With(oidc.HandleCallback).Get("/oauth2/callback", handleLoginCallback)
r.Get("/logout", handleLogout)
r.Get("/login", handleLogin)
r.Get("/logout/redirect", handleLogoutRedirect)
r.Route("/api", func(r chi.Router) {
r.Use(cors.New(cors.Options{
AllowedOrigins: config.HTTP.CORS.AllowedOrigins,
AllowCredentials: config.HTTP.CORS.AllowCredentials,
Debug: config.Debug,
}).Handler)
r.Use(session.UserEmailMiddleware)
gql := handler.New(
generated.NewExecutableSchema(generated.Config{
Resolvers: &graph.Resolver{},
}),
)
gql.AddTransport(transport.POST{})
gql.AddTransport(&transport.Websocket{
KeepAlivePingInterval: 10 * time.Second,
Upgrader: websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
// TODO Check WS connection origin
return true
},
ReadBufferSize: 1024,
WriteBufferSize: 1024,
},
})
if config.Debug {
gql.Use(extension.Introspection{})
r.Get("/v1/playground", playground.Handler("GraphQL playground", "/api/v1/graphql"))
}
r.Handle("/v1/graphql", gql)
})
clientIndex := path.Join(config.HTTP.PublicDir, "index.html")
serveClientIndex := func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, clientIndex)
}
r.Get("/profile", serveClientIndex)
// Serve static files
notFoundHandler := r.NotFoundHandler()
r.Get("/*", static.Dir(config.HTTP.PublicDir, "", notFoundHandler))
return nil
}