-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[CLI-135] feat: add custom template edition w/preview (#267)
* feat: add custom template edition w/preview [CLI-135] * chore: apply PR feedback * chore: add PR feedback * chore: switch to fsnotify for fs events * Update tests for auth, re-org * Update to use AskBool * Remove unused NotifyFileCreated * Simplify: made the templates a const for now * Nit: use URL / ID for names to comply with abbrev convention * Nit: simplify naming within the context of the branding funcs No need for full `templateData` since those functions all deal with templates. * Use structured URL * Also close the listener server.Close doesn't close it, best I can tell. * Fix fsnotify and vim issues TL;DR we need to watch an entire dir instead of just one file. But also, when doing that, just utilizing the entire tmpdir is overkill since that's got many many files in there -- which in practice causes vim to segfault. The fix is simple: make a tempdir, make the tempfile within that, and clean those up in reverse order. * Add docs for branding.Client struct * Add TemplateData docs * Re-jigger status * Cleanup random log.Fatal * Simplify parallel code with errgroup * Apply suggestions from code review * thread ctx; fix lint * chore: return an empty brandinInfo * chore: do not return err on get branding * Added custom domain requirement to the help text Co-authored-by: Cyril David <[email protected]> Co-authored-by: Rita Zerrizuela <[email protected]>
- Loading branch information
1 parent
67e75d3
commit 60c02ca
Showing
49 changed files
with
1,450 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
//go:generate mockgen -source=branding.go -destination=branding_mock.go -package=auth0 | ||
package auth0 | ||
|
||
import "gopkg.in/auth0.v5/management" | ||
|
||
type BrandingAPI interface { | ||
Read(opts ...management.RequestOption) (b *management.Branding, err error) | ||
UniversalLogin(opts ...management.RequestOption) (ul *management.BrandingUniversalLogin, err error) | ||
SetUniversalLogin(ul *management.BrandingUniversalLogin, opts ...management.RequestOption) (err error) | ||
DeleteUniversalLogin(opts ...management.RequestOption) (err error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
//go:generate mockgen -source=tenant.go -destination=tenant_mock.go -package=auth0 | ||
package auth0 | ||
|
||
import "gopkg.in/auth0.v5/management" | ||
|
||
type TenantAPI interface { | ||
Read(opts ...management.RequestOption) (t *management.Tenant, err error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
package branding | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"net" | ||
"net/http" | ||
"net/url" | ||
"path/filepath" | ||
"text/template" | ||
"time" | ||
|
||
"github.com/auth0/auth0-cli/internal/open" | ||
"github.com/fsnotify/fsnotify" | ||
"github.com/guiguan/caster" | ||
) | ||
|
||
// Client is a minimal representation of an auth0 Client as defined in the | ||
// management API. This is used within the branding machinery to populate the | ||
// tenant data. | ||
type Client struct { | ||
ID string `json:"id"` | ||
Name string `json:"name"` | ||
LogoURL string `json:"logo_url,omitempty"` | ||
} | ||
|
||
// TemplateData contains all the variables we project onto our embedded go | ||
// template. These variables largely resemble the same ones in the auth0 | ||
// branding template. | ||
type TemplateData struct { | ||
Filename string | ||
Clients []Client | ||
PrimaryColor string | ||
BackgroundColor string | ||
LogoURL string | ||
TenantName string | ||
Body string | ||
} | ||
|
||
func PreviewCustomTemplate(ctx context.Context, data TemplateData) error { | ||
ctx, cancel := context.WithCancel(ctx) | ||
defer cancel() | ||
|
||
listener, err := net.Listen("tcp", "127.0.0.1:0") | ||
if err != nil { | ||
return err | ||
} | ||
defer listener.Close() | ||
|
||
// Long polling waiting for file changes | ||
broadcaster, err := broadcastCustomTemplateChanges(ctx, data.Filename) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
requestTimeout := 10 * time.Minute | ||
server := &http.Server{ | ||
Handler: buildRoutes(ctx, requestTimeout, data, broadcaster), | ||
ReadTimeout: requestTimeout + 1*time.Minute, | ||
WriteTimeout: requestTimeout + 1*time.Minute, | ||
} | ||
defer server.Close() | ||
|
||
go func() { | ||
if err = server.Serve(listener); err != http.ErrServerClosed { | ||
cancel() | ||
} | ||
}() | ||
|
||
u := &url.URL{ | ||
Scheme: "http", | ||
Host: listener.Addr().String(), | ||
Path: "/data/storybook/", | ||
RawQuery: (url.Values{ | ||
"path": []string{"/story/universal-login--prompts"}, | ||
}).Encode(), | ||
} | ||
|
||
if err := open.URL(u.String()); err != nil { | ||
return err | ||
} | ||
|
||
// Wait until the file is closed or input is cancelled | ||
<-ctx.Done() | ||
return nil | ||
} | ||
|
||
func buildRoutes(ctx context.Context, requestTimeout time.Duration, data TemplateData, broadcaster *caster.Caster) *http.ServeMux { | ||
router := http.NewServeMux() | ||
|
||
router.HandleFunc("/dynamic/events", func(w http.ResponseWriter, r *http.Request) { | ||
ctx := r.Context() | ||
|
||
changes, _ := broadcaster.Sub(ctx, 1) | ||
defer broadcaster.Unsub(changes) | ||
|
||
writeStatus := func(w http.ResponseWriter, code int) { | ||
msg := fmt.Sprintf("%d - %s", code, http.StatusText(http.StatusGone)) | ||
http.Error(w, msg, code) | ||
} | ||
|
||
select { | ||
case <-ctx.Done(): | ||
writeStatus(w, http.StatusGone) | ||
case <-time.After(requestTimeout): | ||
writeStatus(w, http.StatusRequestTimeout) | ||
case <-changes: | ||
writeStatus(w, http.StatusOK) | ||
} | ||
}) | ||
|
||
// The template file | ||
router.HandleFunc("/dynamic/template", func(w http.ResponseWriter, r *http.Request) { | ||
http.ServeFile(w, r, data.Filename) | ||
}) | ||
|
||
jstmpl := template.Must(template.New("tenant-data.js").Funcs(template.FuncMap{ | ||
"asJS": func(v interface{}) string { | ||
a, _ := json.Marshal(v) | ||
return string(a) | ||
}, | ||
}).Parse(tenantDataAsset)) | ||
|
||
router.HandleFunc("/dynamic/tenant-data", func(w http.ResponseWriter, r *http.Request) { | ||
err := jstmpl.Execute(w, data) | ||
if err != nil { | ||
http.Error(w, err.Error(), 500) | ||
} | ||
}) | ||
|
||
// Storybook assets | ||
router.Handle("/", http.FileServer(http.FS(templatePreviewAssets))) | ||
|
||
return router | ||
} | ||
|
||
func broadcastCustomTemplateChanges(ctx context.Context, filename string) (*caster.Caster, error) { | ||
publisher := caster.New(ctx) | ||
|
||
watcher, err := fsnotify.NewWatcher() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
go func() { | ||
for { | ||
select { | ||
case _, ok := <-watcher.Events: | ||
if !ok { | ||
return | ||
} | ||
publisher.Pub(true) | ||
|
||
case _, ok := <-watcher.Errors: | ||
if !ok { | ||
return | ||
} | ||
} | ||
} | ||
}() | ||
|
||
go func() { | ||
<-ctx.Done() | ||
watcher.Close() | ||
publisher.Close() | ||
}() | ||
|
||
if err := watcher.Add(filepath.Dir(filename)); err != nil { | ||
return nil, err | ||
} | ||
|
||
return publisher, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package branding | ||
|
||
import ( | ||
"embed" | ||
_ "embed" | ||
) | ||
|
||
var ( | ||
//go:embed data/storybook/* | ||
templatePreviewAssets embed.FS | ||
|
||
//go:embed data/tenant-data.js | ||
tenantDataAsset string | ||
|
||
//go:embed data/default-template.liquid | ||
DefaultTemplate string | ||
|
||
//go:embed data/footer-template.liquid | ||
FooterTemplate string | ||
|
||
//go:embed data/image-template.liquid | ||
ImageTemplate string | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
{%- auth0:head -%} | ||
</head> | ||
<body> | ||
{%- auth0:widget -%} | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<!DOCTYPE html> | ||
<html lang="{{locale}}"> | ||
<head> | ||
{%- auth0:head -%} | ||
<style> | ||
body { | ||
background-image: radial-gradient(white, rgb(200, 200, 200)); | ||
} | ||
.footer { | ||
background-color: rgb(120, 120, 120); | ||
position: absolute; | ||
bottom: 0; | ||
left: 0; | ||
padding: 16px 0; | ||
width: 100%; | ||
color: white; | ||
/* Use a high z-index for future-proofing */ | ||
z-index: 10; | ||
} | ||
.footer ul { | ||
text-align: center; | ||
} | ||
.footer ul li { | ||
display: inline-block; | ||
margin: 0 4px; | ||
} | ||
.footer ul li:not(:first-of-type) { | ||
margin-left: 0; | ||
} | ||
.footer ul li:not(:first-of-type)::before { | ||
content: ''; | ||
display: inline-block; | ||
vertical-align: middle; | ||
width: 4px; | ||
height: 4px; | ||
margin-right: 4px; | ||
background-color: white; | ||
border-radius: 50%; | ||
} | ||
.footer a { | ||
color: white; | ||
} | ||
</style> | ||
<title>{{ prompt.screen.texts.pageTitle }}</title> | ||
</head> | ||
<body class="_widget-auto-layout"> | ||
{%- auth0:widget -%} | ||
<footer class="footer"> | ||
<ul> | ||
<li><a href="https://company.com/privacy">Privacy Policy</a></li> | ||
<li><a href="https://company.com/terms">Terms of Service</a></li> | ||
</ul> | ||
</footer> | ||
</body> | ||
</html> |
Oops, something went wrong.