package-installer-cli 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/dist/commands/email.js +1140 -0
  3. data/dist/email-templates/bugReport.js +108 -0
  4. data/dist/email-templates/collectors/bugReport.js +41 -0
  5. data/dist/email-templates/collectors/common.js +56 -0
  6. data/dist/email-templates/collectors/docs.js +41 -0
  7. data/dist/email-templates/collectors/featureRequest.js +47 -0
  8. data/dist/email-templates/collectors/improvement.js +52 -0
  9. data/dist/email-templates/collectors/index.js +8 -0
  10. data/dist/email-templates/collectors/question.js +31 -0
  11. data/dist/email-templates/collectors/templateRequest.js +53 -0
  12. data/dist/email-templates/docs.js +111 -0
  13. data/dist/email-templates/featureRequest.js +111 -0
  14. data/dist/email-templates/generator.js +62 -0
  15. data/dist/email-templates/improvement.js +121 -0
  16. data/dist/email-templates/index.js +14 -0
  17. data/dist/email-templates/question.js +101 -0
  18. data/dist/email-templates/styles.js +168 -0
  19. data/dist/email-templates/templateRequest.js +120 -0
  20. data/dist/email-templates/testEmail.js +56 -0
  21. data/dist/email-templates/types.js +1 -0
  22. data/dist/index.js +29 -1
  23. data/dist/utils/featureInstaller.js +4 -11
  24. data/features/ai/ai.json +547 -0
  25. data/features/analytics/analytics.json +4 -0
  26. data/features/auth/auth.json +473 -0
  27. data/features/auth/auth0/django/backend/apiexample/urls.py +10 -0
  28. data/features/auth/auth0/django/backend/apiexample/validator.py +21 -0
  29. data/features/auth/auth0/django/backend/apiexample/views.py +30 -0
  30. data/features/auth/auth0/django/backend/requirements.txt +4 -0
  31. data/features/auth/auth0/django/web-app/requirements.txt +4 -0
  32. data/features/auth/auth0/django/web-app/webappexample/settings.py +28 -0
  33. data/features/auth/auth0/django/web-app/webappexample/templates/index.html +16 -0
  34. data/features/auth/auth0/django/web-app/webappexample/urls.py +10 -0
  35. data/features/auth/auth0/django/web-app/webappexample/views.py +52 -0
  36. data/features/auth/auth0/go/backend/go.mod +8 -0
  37. data/features/auth/auth0/go/backend/main.go +60 -0
  38. data/features/auth/auth0/go/backend/middleware/jwt.go +81 -0
  39. data/features/auth/auth0/go/web-app/auth.go +56 -0
  40. data/features/auth/auth0/go/web-app/callback.go +52 -0
  41. data/features/auth/auth0/go/web-app/go.mod +11 -0
  42. data/features/auth/auth0/go/web-app/isAuthenticated.go +20 -0
  43. data/features/auth/auth0/go/web-app/login.go +47 -0
  44. data/features/auth/auth0/go/web-app/logout.go +38 -0
  45. data/features/auth/auth0/go/web-app/main.go +31 -0
  46. data/features/auth/auth0/go/web-app/router.go +44 -0
  47. data/features/auth/auth0/go/web-app/user.go +18 -0
  48. data/features/auth/auth0/ruby-on-rails/backend/Gemfile +1 -0
  49. data/features/auth/auth0/ruby-on-rails/backend/app/controllers/application_controller.rb +5 -0
  50. data/features/auth/auth0/ruby-on-rails/backend/app/controllers/concern/secured.rb +60 -0
  51. data/features/auth/auth0/ruby-on-rails/backend/app/controllers/private_controller.rb +6 -0
  52. data/features/auth/auth0/ruby-on-rails/backend/app/controllers/public-controller.rb +6 -0
  53. data/features/auth/auth0/ruby-on-rails/backend/app/lib/auth0_client.rb +59 -0
  54. data/features/auth/auth0/ruby-on-rails/web-app/Gemfile +2 -0
  55. data/features/auth/auth0/ruby-on-rails/web-app/auth0_controller.rb +41 -0
  56. data/features/auth/auth0/ruby-on-rails/web-app/config/auth0.yml +4 -0
  57. data/features/auth/auth0/ruby-on-rails/web-app/config/initializers/auth0.rb +14 -0
  58. data/features/auth/auth0/ruby-on-rails/web-app/config/routes.rb +6 -0
  59. data/features/auth/auth0/ruby-on-rails/web-app/config/secured.rb +11 -0
  60. data/features/auth/clerk/go/clerk_client.go +28 -0
  61. data/features/auth/clerk/go/go.mod +5 -0
  62. data/features/auth/clerk/go/main.go +82 -0
  63. data/features/auth/clerk/nextjs/typescript/app/layout.tsx +2 -2
  64. data/features/auth/clerk/ruby-on-rails/Gemfile +1 -0
  65. data/features/auth/clerk/ruby-on-rails/app.rb +50 -0
  66. data/features/auth/clerk/ruby-on-rails/config/initializers/clerk.rb +4 -0
  67. data/features/aws/aws.json +5207 -0
  68. data/features/database/database.json +246 -0
  69. data/features/docker/docker.json +108 -0
  70. data/features/features.json +44 -7321
  71. data/features/gitignore/gitignore.json +61 -0
  72. data/features/monitoring/monitoring.json +5 -0
  73. data/features/payment/payment.json +347 -0
  74. data/features/storage/storage.json +371 -0
  75. data/features/testing/jest/angularjs/typescript/tests/angularjs.test.ts +12 -0
  76. data/features/testing/jest/expressjs/javascript/tests/expressjs.test.js +10 -0
  77. data/features/testing/jest/expressjs/typescript/tests/expressjs.test.ts +10 -0
  78. data/features/testing/jest/nestjs/typescript/tests/nestjs.test.ts +10 -0
  79. data/features/testing/jest/nextjs/javascript/tests/nextjs.test.js +10 -0
  80. data/features/testing/jest/nextjs/typescript/tests/nextjs.test.ts +10 -0
  81. data/features/testing/jest/nuxtjs/typescript/tests/nuxtjs.test.ts +9 -0
  82. data/features/testing/jest/reactjs/javascript/tests/reactjs.test.js +10 -0
  83. data/features/testing/jest/reactjs/typescript/tests/reactjs.test.ts +10 -0
  84. data/features/testing/jest/reactjs-expressjs-shadcn/javascript/tests/reactjs.expressjs.shadcn.test.js +16 -0
  85. data/features/testing/jest/reactjs-expressjs-shadcn/typescript/tests/reactjs.expressjs.shadcn.test.ts +16 -0
  86. data/features/testing/jest/reactjs-nestjs-shadcn/typescript/tests/reactjs.nestjs.shadcn.test.ts +14 -0
  87. data/features/testing/jest/remixjs/typescript/tests/remixjs.test.ts +9 -0
  88. data/features/testing/jest/vuejs/javascript/tests/vuejs.test.ts +9 -0
  89. data/features/testing/jest/vuejs/typescript/tests/vuejs.test.ts +9 -0
  90. data/features/testing/testing.json +102 -0
  91. data/features/ui/ui.json +91 -0
  92. metadata +88 -8
  93. data/features/testing/jest/angularjs/tests/angularjs.test.js +0 -12
  94. data/features/testing/jest/expressjs/tests/javascript/expressjs.test.js +0 -12
  95. data/features/testing/jest/expressjs/tests/typescript/expressjs.test.ts +0 -12
  96. data/features/testing/jest/go/tests/go.test.js +0 -12
  97. data/features/testing/jest/nextjs/tests/javascript/nextjs.test.js +0 -12
  98. data/features/testing/jest/nextjs/tests/typescript/nextjs.test.ts +0 -12
@@ -0,0 +1,60 @@
1
+ package main
2
+
3
+ import (
4
+ "01-Authorization-RS256/middleware"
5
+ "log"
6
+ "net/http"
7
+
8
+ jwtmiddleware "github.com/auth0/go-jwt-middleware/v2"
9
+ "github.com/auth0/go-jwt-middleware/v2/validator"
10
+ "github.com/joho/godotenv"
11
+ )
12
+
13
+ func main() {
14
+ if err := godotenv.Load(); err != nil {
15
+ log.Fatalf("Error loading the .env file: %v", err)
16
+ }
17
+
18
+ router := http.NewServeMux()
19
+
20
+ // This route is always accessible.
21
+ router.Handle("/api/public", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22
+ w.Header().Set("Content-Type", "application/json")
23
+ w.WriteHeader(http.StatusOK)
24
+ w.Write([]byte(`{"message":"Hello from a public endpoint! You don't need to be authenticated to see this."}`))
25
+ }))
26
+
27
+ // This route is only accessible if the user has a valid access_token.
28
+ router.Handle("/api/private", middleware.EnsureValidToken()(
29
+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
30
+ w.Header().Set("Content-Type", "application/json")
31
+ w.WriteHeader(http.StatusOK)
32
+ w.Write([]byte(`{"message":"Hello from a private endpoint! You need to be authenticated to see this."}`))
33
+ }),
34
+ ))
35
+
36
+ // This route is only accessible if the user has a
37
+ // valid access_token with the read:messages scope.
38
+ router.Handle("/api/private-scoped", middleware.EnsureValidToken()(
39
+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
40
+ w.Header().Set("Content-Type", "application/json")
41
+
42
+ token := r.Context().Value(jwtmiddleware.ContextKey{}).(*validator.ValidatedClaims)
43
+
44
+ claims := token.CustomClaims.(*middleware.CustomClaims)
45
+ if !claims.HasScope("read:messages") {
46
+ w.WriteHeader(http.StatusForbidden)
47
+ w.Write([]byte(`{"message":"Insufficient scope."}`))
48
+ return
49
+ }
50
+
51
+ w.WriteHeader(http.StatusOK)
52
+ w.Write([]byte(`{"message":"Hello from a private endpoint! You need to be authenticated to see this."}`))
53
+ }),
54
+ ))
55
+
56
+ log.Print("Server listening on http://localhost:3010")
57
+ if err := http.ListenAndServe("0.0.0.0:3010", router); err != nil {
58
+ log.Fatalf("There was an error with the http server: %v", err)
59
+ }
60
+ }
@@ -0,0 +1,81 @@
1
+ package middleware
2
+
3
+ import (
4
+ "context"
5
+ "log"
6
+ "net/http"
7
+ "net/url"
8
+ "os"
9
+ "strings"
10
+ "time"
11
+
12
+ jwtmiddleware "github.com/auth0/go-jwt-middleware/v2"
13
+ "github.com/auth0/go-jwt-middleware/v2/jwks"
14
+ "github.com/auth0/go-jwt-middleware/v2/validator"
15
+ )
16
+
17
+ // CustomClaims contains custom data we want from the token.
18
+ type CustomClaims struct {
19
+ Scope string `json:"scope"`
20
+ }
21
+
22
+ // Validate does nothing for this example, but we need
23
+ // it to satisfy validator.CustomClaims interface.
24
+ func (c CustomClaims) Validate(ctx context.Context) error {
25
+ return nil
26
+ }
27
+
28
+ // EnsureValidToken is a middleware that will check the validity of our JWT.
29
+ func EnsureValidToken() func(next http.Handler) http.Handler {
30
+ issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/")
31
+ if err != nil {
32
+ log.Fatalf("Failed to parse the issuer url: %v", err)
33
+ }
34
+
35
+ provider := jwks.NewCachingProvider(issuerURL, 5*time.Minute)
36
+
37
+ jwtValidator, err := validator.New(
38
+ provider.KeyFunc,
39
+ validator.RS256,
40
+ issuerURL.String(),
41
+ []string{os.Getenv("AUTH0_AUDIENCE")},
42
+ validator.WithCustomClaims(
43
+ func() validator.CustomClaims {
44
+ return &CustomClaims{}
45
+ },
46
+ ),
47
+ validator.WithAllowedClockSkew(time.Minute),
48
+ )
49
+ if err != nil {
50
+ log.Fatalf("Failed to set up the jwt validator")
51
+ }
52
+
53
+ errorHandler := func(w http.ResponseWriter, r *http.Request, err error) {
54
+ log.Printf("Encountered error while validating JWT: %v", err)
55
+
56
+ w.Header().Set("Content-Type", "application/json")
57
+ w.WriteHeader(http.StatusUnauthorized)
58
+ w.Write([]byte(`{"message":"Failed to validate JWT."}`))
59
+ }
60
+
61
+ middleware := jwtmiddleware.New(
62
+ jwtValidator.ValidateToken,
63
+ jwtmiddleware.WithErrorHandler(errorHandler),
64
+ )
65
+
66
+ return func(next http.Handler) http.Handler {
67
+ return middleware.CheckJWT(next)
68
+ }
69
+ }
70
+
71
+ // HasScope checks whether our claims have a specific scope.
72
+ func (c CustomClaims) HasScope(expectedScope string) bool {
73
+ result := strings.Split(c.Scope, " ")
74
+ for i := range result {
75
+ if result[i] == expectedScope {
76
+ return true
77
+ }
78
+ }
79
+
80
+ return false
81
+ }
@@ -0,0 +1,56 @@
1
+ // Save this file in ./platform/authenticator/auth.go
2
+
3
+ package authenticator
4
+
5
+ import (
6
+ "context"
7
+ "errors"
8
+ "os"
9
+
10
+ "github.com/coreos/go-oidc/v3/oidc"
11
+ "golang.org/x/oauth2"
12
+ )
13
+
14
+ // Authenticator is used to authenticate our users.
15
+ type Authenticator struct {
16
+ *oidc.Provider
17
+ oauth2.Config
18
+ }
19
+
20
+ // New instantiates the *Authenticator.
21
+ func New() (*Authenticator, error) {
22
+ provider, err := oidc.NewProvider(
23
+ context.Background(),
24
+ "https://"+os.Getenv("AUTH0_DOMAIN")+"/",
25
+ )
26
+ if err != nil {
27
+ return nil, err
28
+ }
29
+
30
+ conf := oauth2.Config{
31
+ ClientID: os.Getenv("AUTH0_CLIENT_ID"),
32
+ ClientSecret: os.Getenv("AUTH0_CLIENT_SECRET"),
33
+ RedirectURL: os.Getenv("AUTH0_CALLBACK_URL"),
34
+ Endpoint: provider.Endpoint(),
35
+ Scopes: []string{oidc.ScopeOpenID, "profile"},
36
+ }
37
+
38
+ return &Authenticator{
39
+ Provider: provider,
40
+ Config: conf,
41
+ }, nil
42
+ }
43
+
44
+ // VerifyIDToken verifies that an *oauth2.Token is a valid *oidc.IDToken.
45
+ func (a *Authenticator) VerifyIDToken(ctx context.Context, token *oauth2.Token) (*oidc.IDToken, error) {
46
+ rawIDToken, ok := token.Extra("id_token").(string)
47
+ if !ok {
48
+ return nil, errors.New("no id_token field in oauth2 token")
49
+ }
50
+
51
+ oidcConfig := &oidc.Config{
52
+ ClientID: a.ClientID,
53
+ }
54
+
55
+ return a.Verifier(oidcConfig).Verify(ctx, rawIDToken)
56
+ }
@@ -0,0 +1,52 @@
1
+ / Save this file in ./web/app/callback/callback.go
2
+
3
+ package callback
4
+
5
+ import (
6
+ "net/http"
7
+
8
+ "github.com/gin-contrib/sessions"
9
+ "github.com/gin-gonic/gin"
10
+
11
+ "01-Login/platform/authenticator"
12
+ )
13
+
14
+ // Handler for our callback.
15
+ func Handler(auth *authenticator.Authenticator) gin.HandlerFunc {
16
+ return func(ctx *gin.Context) {
17
+ session := sessions.Default(ctx)
18
+ if ctx.Query("state") != session.Get("state") {
19
+ ctx.String(http.StatusBadRequest, "Invalid state parameter.")
20
+ return
21
+ }
22
+
23
+ // Exchange an authorization code for a token.
24
+ token, err := auth.Exchange(ctx.Request.Context(), ctx.Query("code"))
25
+ if err != nil {
26
+ ctx.String(http.StatusUnauthorized, "Failed to exchange an authorization code for a token.")
27
+ return
28
+ }
29
+
30
+ idToken, err := auth.VerifyIDToken(ctx.Request.Context(), token)
31
+ if err != nil {
32
+ ctx.String(http.StatusInternalServerError, "Failed to verify ID Token.")
33
+ return
34
+ }
35
+
36
+ var profile map[string]interface{}
37
+ if err := idToken.Claims(&profile); err != nil {
38
+ ctx.String(http.StatusInternalServerError, err.Error())
39
+ return
40
+ }
41
+
42
+ session.Set("access_token", token.AccessToken)
43
+ session.Set("profile", profile)
44
+ if err := session.Save(); err != nil {
45
+ ctx.String(http.StatusInternalServerError, err.Error())
46
+ return
47
+ }
48
+
49
+ // Redirect to logged in page.
50
+ ctx.Redirect(http.StatusTemporaryRedirect, "/user")
51
+ }
52
+ }
@@ -0,0 +1,11 @@
1
+ module 01-Login
2
+
3
+ go 1.21
4
+
5
+ require (
6
+ github.com/coreos/go-oidc/v3 v3.8.0
7
+ github.com/gin-contrib/sessions v0.0.5
8
+ github.com/gin-gonic/gin v1.9.1
9
+ github.com/joho/godotenv v1.5.1
10
+ golang.org/x/oauth2 v0.15.0
11
+ )
@@ -0,0 +1,20 @@
1
+ // Save this file in ./platform/middleware/isAuthenticated.go
2
+
3
+ package middleware
4
+
5
+ import (
6
+ "net/http"
7
+
8
+ "github.com/gin-contrib/sessions"
9
+ "github.com/gin-gonic/gin"
10
+ )
11
+
12
+ // IsAuthenticated is a middleware that checks if
13
+ // the user has already been authenticated previously.
14
+ func IsAuthenticated(ctx *gin.Context) {
15
+ if sessions.Default(ctx).Get("profile") == nil {
16
+ ctx.Redirect(http.StatusSeeOther, "/")
17
+ } else {
18
+ ctx.Next()
19
+ }
20
+ }
@@ -0,0 +1,47 @@
1
+ // Save this file in ./web/app/login/login.go
2
+
3
+ package login
4
+
5
+ import (
6
+ "crypto/rand"
7
+ "encoding/base64"
8
+ "net/http"
9
+
10
+ "github.com/gin-contrib/sessions"
11
+ "github.com/gin-gonic/gin"
12
+
13
+ "01-Login/platform/authenticator"
14
+ )
15
+
16
+ // Handler for our login.
17
+ func Handler(auth *authenticator.Authenticator) gin.HandlerFunc {
18
+ return func(ctx *gin.Context) {
19
+ state, err := generateRandomState()
20
+ if err != nil {
21
+ ctx.String(http.StatusInternalServerError, err.Error())
22
+ return
23
+ }
24
+
25
+ // Save the state inside the session.
26
+ session := sessions.Default(ctx)
27
+ session.Set("state", state)
28
+ if err := session.Save(); err != nil {
29
+ ctx.String(http.StatusInternalServerError, err.Error())
30
+ return
31
+ }
32
+
33
+ ctx.Redirect(http.StatusTemporaryRedirect, auth.AuthCodeURL(state))
34
+ }
35
+ }
36
+
37
+ func generateRandomState() (string, error) {
38
+ b := make([]byte, 32)
39
+ _, err := rand.Read(b)
40
+ if err != nil {
41
+ return "", err
42
+ }
43
+
44
+ state := base64.StdEncoding.EncodeToString(b)
45
+
46
+ return state, nil
47
+ }
@@ -0,0 +1,38 @@
1
+ // Save this file in ./web/app/logout/logout.go
2
+
3
+ package logout
4
+
5
+ import (
6
+ "net/http"
7
+ "net/url"
8
+ "os"
9
+
10
+ "github.com/gin-gonic/gin"
11
+ )
12
+
13
+ // Handler for our logout.
14
+ func Handler(ctx *gin.Context) {
15
+ logoutUrl, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/v2/logout")
16
+ if err != nil {
17
+ ctx.String(http.StatusInternalServerError, err.Error())
18
+ return
19
+ }
20
+
21
+ scheme := "http"
22
+ if ctx.Request.TLS != nil {
23
+ scheme = "https"
24
+ }
25
+
26
+ returnTo, err := url.Parse(scheme + "://" + ctx.Request.Host)
27
+ if err != nil {
28
+ ctx.String(http.StatusInternalServerError, err.Error())
29
+ return
30
+ }
31
+
32
+ parameters := url.Values{}
33
+ parameters.Add("returnTo", returnTo.String())
34
+ parameters.Add("client_id", os.Getenv("AUTH0_CLIENT_ID"))
35
+ logoutUrl.RawQuery = parameters.Encode()
36
+
37
+ ctx.Redirect(http.StatusTemporaryRedirect, logoutUrl.String())
38
+ }
@@ -0,0 +1,31 @@
1
+ // Save this file in ./main.go
2
+
3
+ package main
4
+
5
+ import (
6
+ "log"
7
+ "net/http"
8
+
9
+ "github.com/joho/godotenv"
10
+
11
+ "01-Login/platform/authenticator"
12
+ "01-Login/platform/router"
13
+ )
14
+
15
+ func main() {
16
+ if err := godotenv.Load(); err != nil {
17
+ log.Fatalf("Failed to load the env vars: %v", err)
18
+ }
19
+
20
+ auth, err := authenticator.New()
21
+ if err != nil {
22
+ log.Fatalf("Failed to initialize the authenticator: %v", err)
23
+ }
24
+
25
+ rtr := router.New(auth)
26
+
27
+ log.Print("Server listening on http://localhost:3000/")
28
+ if err := http.ListenAndServe("0.0.0.0:3000", rtr); err != nil {
29
+ log.Fatalf("There was an error with the http server: %v", err)
30
+ }
31
+ }
@@ -0,0 +1,44 @@
1
+ // Save this file in ./platform/router/router.go
2
+
3
+ package router
4
+
5
+ import (
6
+ "encoding/gob"
7
+ "net/http"
8
+
9
+ "github.com/gin-contrib/sessions"
10
+ "github.com/gin-contrib/sessions/cookie"
11
+ "github.com/gin-gonic/gin"
12
+
13
+ "01-Login/platform/authenticator"
14
+ "01-Login/platform/middleware"
15
+ "01-Login/web/app/callback"
16
+ "01-Login/web/app/login"
17
+ "01-Login/web/app/logout"
18
+ "01-Login/web/app/user"
19
+ )
20
+
21
+ // New registers the routes and returns the router.
22
+ func New(auth *authenticator.Authenticator) *gin.Engine {
23
+ router := gin.Default()
24
+
25
+ // To store custom types in our cookies,
26
+ // we must first register them using gob.Register
27
+ gob.Register(map[string]interface{}{})
28
+
29
+ store := cookie.NewStore([]byte("secret"))
30
+ router.Use(sessions.Sessions("auth-session", store))
31
+
32
+ router.Static("/public", "web/static")
33
+ router.LoadHTMLGlob("web/template/*")
34
+
35
+ router.GET("/", func(ctx *gin.Context) {
36
+ ctx.HTML(http.StatusOK, "home.html", nil)
37
+ })
38
+ router.GET("/login", login.Handler(auth))
39
+ router.GET("/callback", callback.Handler(auth))
40
+ router.GET("/user", user.Handler)
41
+ router.GET("/logout", logout.Handler)
42
+
43
+ return router
44
+ }
@@ -0,0 +1,18 @@
1
+ // Save this file in ./web/app/user/user.go
2
+
3
+ package user
4
+
5
+ import (
6
+ "net/http"
7
+
8
+ "github.com/gin-contrib/sessions"
9
+ "github.com/gin-gonic/gin"
10
+ )
11
+
12
+ // Handler for our logged-in user page.
13
+ func Handler(ctx *gin.Context) {
14
+ session := sessions.Default(ctx)
15
+ profile := session.Get("profile")
16
+
17
+ ctx.HTML(http.StatusOK, "user.html", profile)
18
+ }
@@ -0,0 +1 @@
1
+ gem 'jwt'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationController < ActionController::API
4
+ include Secured
5
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Secured
4
+ extend ActiveSupport::Concern
5
+
6
+ REQUIRES_AUTHENTICATION = { message: 'Requires authentication' }.freeze
7
+ BAD_CREDENTIALS = {
8
+ message: 'Bad credentials'
9
+ }.freeze
10
+ MALFORMED_AUTHORIZATION_HEADER = {
11
+ error: 'invalid_request',
12
+ error_description: 'Authorization header value must follow this format: Bearer access-token',
13
+ message: 'Bad credentials'
14
+ }.freeze
15
+ INSUFFICIENT_PERMISSIONS = {
16
+ error: 'insufficient_permissions',
17
+ error_description: 'The access token does not contain the required permissions',
18
+ message: 'Permission denied'
19
+ }.freeze
20
+
21
+ def authorize
22
+ token = token_from_request
23
+
24
+ return if performed?
25
+
26
+ validation_response = Auth0Client.validate_token(token)
27
+
28
+ @decoded_token = validation_response.decoded_token
29
+
30
+ return unless (error = validation_response.error)
31
+
32
+ render json: { message: error.message }, status: error.status
33
+ end
34
+
35
+ def validate_permissions(permissions)
36
+ raise 'validate_permissions needs to be called with a block' unless block_given?
37
+ return yield if @decoded_token.validate_permissions(permissions)
38
+
39
+ render json: INSUFFICIENT_PERMISSIONS, status: :forbidden
40
+ end
41
+
42
+ private
43
+
44
+ def token_from_request
45
+ authorization_header_elements = request.headers['Authorization']&.split
46
+
47
+ render json: REQUIRES_AUTHENTICATION, status: :unauthorized and return unless authorization_header_elements
48
+
49
+ unless authorization_header_elements.length == 2
50
+ render json: MALFORMED_AUTHORIZATION_HEADER,
51
+ status: :unauthorized and return
52
+ end
53
+
54
+ scheme, token = authorization_header_elements
55
+
56
+ render json: BAD_CREDENTIALS, status: :unauthorized and return unless scheme.downcase == 'bearer'
57
+
58
+ token
59
+ end
60
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ class PublicController < ApplicationController
3
+ def public
4
+ render json: { message: 'All good. You don\'t need to be authenticated to call this.' }
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ class PublicController < ApplicationController
3
+ def public
4
+ render json: { message: 'All good. You don\'t need to be authenticated to call this.' }
5
+ end
6
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt'
4
+ require 'net/http'
5
+
6
+ # Auth0Client class to handle JWT token validation
7
+ class Auth0Client
8
+ # Auth0 Client Objects
9
+ Error = Struct.new(:message, :status)
10
+ Response = Struct.new(:decoded_token, :error)
11
+ Token = Struct.new(:token) do
12
+ def validate_permissions(permissions)
13
+ required_permissions = Set.new permissions
14
+ scopes = token[0]['scope']
15
+ token_permissions = scopes.present? ? Set.new(scopes.split(" ")) : Set.new
16
+ required_permissions <= token_permissions
17
+ end
18
+ end
19
+
20
+ # Helper Functions
21
+ def self.domain_url
22
+ "https://#{Rails.configuration.auth0.domain}/"
23
+ end
24
+
25
+ def self.decode_token(token, jwks_hash)
26
+ JWT.decode(token, nil, true, {
27
+ algorithm: 'RS256',
28
+ iss: domain_url,
29
+ verify_iss: true,
30
+ aud: Rails.configuration.auth0.audience,
31
+ verify_aud: true,
32
+ jwks: { keys: jwks_hash[:keys] }
33
+ })
34
+ end
35
+
36
+ def self.get_jwks
37
+ jwks_uri = URI("#{domain_url}.well-known/jwks.json")
38
+ Net::HTTP.get_response jwks_uri
39
+ end
40
+
41
+ # Token Validation
42
+ def self.validate_token(token)
43
+ jwks_response = get_jwks
44
+
45
+ unless jwks_response.is_a? Net::HTTPSuccess
46
+ error = Error.new(message: 'Unable to verify credentials', status: :internal_server_error)
47
+ return Response.new(nil, error)
48
+ end
49
+
50
+ jwks_hash = JSON.parse(jwks_response.body).deep_symbolize_keys
51
+
52
+ decoded_token = decode_token(token, jwks_hash)
53
+
54
+ Response.new(Token.new(decoded_token), nil)
55
+ rescue JWT::VerificationError, JWT::DecodeError => e
56
+ error = Error.new('Bad credentials', :unauthorized)
57
+ Response.new(nil, error)
58
+ end
59
+ end
@@ -0,0 +1,2 @@
1
+ gem 'omniauth-auth0', '~> 3.0'
2
+ gem 'omniauth-rails_csrf_protection', '~> 1.0' # prevents forged authentication requests
@@ -0,0 +1,41 @@
1
+ class Auth0Controller < ApplicationController
2
+ def callback
3
+ # OmniAuth stores the informatin returned from
4
+ # Auth0 and the IdP in request.env['omniauth.auth'].
5
+ # In this code, you will pull the raw_info supplied
6
+ # from the id_token and assign it to the session.
7
+ # Refer to https://github.com/auth0/omniauth-auth0/blob/master/EXAMPLES.md#example-of-the-resulting-authentication-hash
8
+ # for complete information on 'omniauth.auth' contents.
9
+ auth_info = request.env['omniauth.auth']
10
+ session[:userinfo] = auth_info['extra']['raw_info']
11
+
12
+ # Redirect to the URL you want after successful auth
13
+ redirect_to '/dashboard'
14
+ end
15
+
16
+ def failure
17
+ # Handles failed authentication
18
+ @error_msg = request.params['message']
19
+ end
20
+
21
+ def logout
22
+ reset_session
23
+ redirect_to logout_url
24
+ end
25
+
26
+ private
27
+ AUTH0_CONFIG = Rails.application.config_for(:auth0)
28
+
29
+ def logout_url
30
+ request_params = {
31
+ returnTo: root_url,
32
+ client_id: AUTH0_CONFIG['auth0_client_id']
33
+ }
34
+
35
+ URI::HTTPS.build(host: AUTH0_CONFIG['auth0_domain'], path: '/v2/logout', query: to_query(request_params)).to_s
36
+ end
37
+
38
+ def to_query(hash)
39
+ hash.map { |k, v| "#{k}=#{CGI.escape(v)}" unless v.nil? }.reject(&:nil?).join('&')
40
+ end
41
+ end
@@ -0,0 +1,4 @@
1
+ development:
2
+ auth0_domain: {yourDomain}
3
+ auth0_client_id: {yourClientId}
4
+ auth0_client_secret: {yourClientSecret}
@@ -0,0 +1,14 @@
1
+ AUTH0_CONFIG = Rails.application.config_for(:auth0)
2
+
3
+ Rails.application.config.middleware.use OmniAuth::Builder do
4
+ provider(
5
+ :auth0,
6
+ AUTH0_CONFIG['auth0_client_id'],
7
+ AUTH0_CONFIG['auth0_client_secret'],
8
+ AUTH0_CONFIG['auth0_domain'],
9
+ callback_path: '/auth/auth0/callback',
10
+ authorize_params: {
11
+ scope: 'openid profile'
12
+ }
13
+ )
14
+ end