feat: add permissions on user create, CORS middleware, cors server playground.
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"flag"
|
"flag"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -48,6 +49,10 @@ type config struct {
|
|||||||
password string
|
password string
|
||||||
sender string
|
sender string
|
||||||
}
|
}
|
||||||
|
// Add a cors struct and trustedOrigins field with the type []string.
|
||||||
|
cors struct {
|
||||||
|
trustedOrigins []string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define an application struct to hold the dependencies for our HTTP handlers, helpers,
|
// Define an application struct to hold the dependencies for our HTTP handlers, helpers,
|
||||||
@@ -101,6 +106,17 @@ func main() {
|
|||||||
flag.StringVar(&cfg.smtp.password, "smtp-password", "d06a774b484ca3", "SMTP password")
|
flag.StringVar(&cfg.smtp.password, "smtp-password", "d06a774b484ca3", "SMTP password")
|
||||||
flag.StringVar(&cfg.smtp.sender, "smtp-sender", "Greenlight <no-reply@greenlight.debuggingjon.dev>", "SMTP sender")
|
flag.StringVar(&cfg.smtp.sender, "smtp-sender", "Greenlight <no-reply@greenlight.debuggingjon.dev>", "SMTP sender")
|
||||||
|
|
||||||
|
// Use the flag.Func() function to process the -cors-trusted-origins command line
|
||||||
|
// flag. In this we use the strings.Fields() function to split the flag value into a
|
||||||
|
// slice based on whitespace characters and assign it to our config struct.
|
||||||
|
// Importantly, if the -cors-trusted-origins flag is not present, contains the empty
|
||||||
|
// string, or contains only whitespace, then strings.Fields() will return an empty
|
||||||
|
// []string slice.
|
||||||
|
flag.Func("cors-trusted-origins", "Trusted CORS origins (space separated)", func(val string) error {
|
||||||
|
cfg.cors.trustedOrigins = strings.Fields(val)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// Call the openDB() helper function (see below) to create the connection pool,
|
// Call the openDB() helper function (see below) to create the connection pool,
|
||||||
|
|||||||
@@ -244,3 +244,30 @@ func (app *application) requirePermission(code string, next http.HandlerFunc) ht
|
|||||||
// Wrap this with the requireActivatedUser() middleware before returning it.
|
// Wrap this with the requireActivatedUser() middleware before returning it.
|
||||||
return app.requireActivatedUser(fn)
|
return app.requireActivatedUser(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *application) enableCORS(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Add the "Vary: Origin" header.
|
||||||
|
w.Header().Add("Vary", "Origin")
|
||||||
|
|
||||||
|
// Get the value of the request's Origin header.
|
||||||
|
origin := r.Header.Get("Origin")
|
||||||
|
|
||||||
|
// Only run this if there's an Origin request header present AND at least one
|
||||||
|
// trusted origin is configured.
|
||||||
|
if origin != "" && len(app.config.cors.trustedOrigins) != 0 {
|
||||||
|
// Loop through the list of trusted origins, checking to see if the request
|
||||||
|
// origin exactly matches one of them.
|
||||||
|
for i := range app.config.cors.trustedOrigins {
|
||||||
|
if origin == app.config.cors.trustedOrigins[i] {
|
||||||
|
// If there is a match, then set a "Access-Control-Allow-Origin
|
||||||
|
// response header with the request origin as the value.
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the next handler in the chain.
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,5 +28,5 @@ func (app *application) routes() http.Handler {
|
|||||||
router.HandlerFunc(http.MethodPost, "/v1/tokens/authentication",
|
router.HandlerFunc(http.MethodPost, "/v1/tokens/authentication",
|
||||||
app.createAuthenticationTokenHandler)
|
app.createAuthenticationTokenHandler)
|
||||||
|
|
||||||
return app.recoverPanic(app.rateLimit(app.authenticate(router)))
|
return app.recoverPanic(app.enableCORS(app.rateLimit(app.authenticate(router))))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,13 @@ func (app *application) registerUserHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the "movies:read" permission for the new user.
|
||||||
|
err = app.models.Permissions.AddForUser(user.ID, "movies:read")
|
||||||
|
if err != nil {
|
||||||
|
app.serverErrorResponse(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// After the user record has been created in the database, generate a new activation
|
// After the user record has been created in the database, generate a new activation
|
||||||
// token for the user.
|
// token for the user.
|
||||||
token, err := app.models.Tokens.New(user.ID, 3*24*time.Hour, data.ScopeActivation)
|
token, err := app.models.Tokens.New(user.ID, 3*24*time.Hour, data.ScopeActivation)
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Define a string constant containing the HTML for the webpage. This consists of a <h1>
|
||||||
|
// header tag, and some JavaScript which calls our POST /v1/tokens/authentication
|
||||||
|
// endpoint and writes the response body to inside the <div id="output"></div> tag.
|
||||||
|
const html = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Preflight CORS</h1>
|
||||||
|
<div id="output"></div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
fetch("http://localhost:4000/v1/tokens/authentication", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: 'alice@example.com',
|
||||||
|
password: 'pa55word'
|
||||||
|
})
|
||||||
|
}).then(
|
||||||
|
function (response) {
|
||||||
|
response.text().then(function (text) {
|
||||||
|
document.getElementById("output").inner
|
||||||
|
t.getElementById("output").innerHTML = text;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
|
document.getElementById("output").innerHTML = err;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
addr := flag.String("addr", ":9000", "Server address")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
log.Printf("starting server on %s", *addr)
|
||||||
|
|
||||||
|
err := http.ListenAndServe(*addr, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(html))
|
||||||
|
}))
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Define a string constant containing the HTML for the webpage. This consists of a <h1>
|
||||||
|
// header tag, and some JavaScript which fetches the JSON from our GET /v1/healthcheck
|
||||||
|
// endpoint and writes it to inside the <div id="output"></div> element.
|
||||||
|
const html = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Simple CORS</h1>
|
||||||
|
<div id="output"></div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
fetch("http://localhost:4000/v1/healthcheck").then(
|
||||||
|
function (response) {
|
||||||
|
response.text().then(function (text) {
|
||||||
|
document.getElementById("output").innerHTML = text;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
|
document.getElementById("output").innerHTML = err;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Make the server address configurable at runtime via a command-line flag.
|
||||||
|
addr := flag.String("addr", ":9000", "Server address")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
log.Printf("starting server on %s", *addr)
|
||||||
|
|
||||||
|
// Start a HTTP server listening on the given address, which responds to all
|
||||||
|
// requests with the webpage HTML above.
|
||||||
|
err := http.ListenAndServe(*addr, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(html))
|
||||||
|
}))
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
@@ -10,8 +10,8 @@ http:
|
|||||||
type: json
|
type: json
|
||||||
data: |-
|
data: |-
|
||||||
{
|
{
|
||||||
"name": "Bob",
|
"name": "Grace",
|
||||||
"email": "bob@example.com",
|
"email": "grace@example.com",
|
||||||
"password": "pa55word"
|
"password": "pa55word"
|
||||||
}
|
}
|
||||||
auth: inherit
|
auth: inherit
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Define a Permissions slice, which we will use to will hold the permission codes (like
|
// Define a Permissions slice, which we will use to will hold the permission codes (like
|
||||||
@@ -64,3 +66,18 @@ func (m PermissionModel) GetAllForUser(userID int64) (Permissions, error) {
|
|||||||
|
|
||||||
return permissions, nil
|
return permissions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddForUser - Add the provided permission codes for a specific user. Notice that we're using a
|
||||||
|
// variadic parameter for the codes so that we can assign multiple permissions in a
|
||||||
|
// single call.
|
||||||
|
func (m PermissionModel) AddForUser(userID int64, codes ...string) error {
|
||||||
|
query := `
|
||||||
|
INSERT INTO users_permissions
|
||||||
|
SELECT $1, permissions.id FROM permissions WHERE permissions.code = ANY($2)`
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := m.DB.ExecContext(ctx, query, userID, pq.Array(codes))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user