From f28da98d2790db7f251ba05598ab478f3bef37aa Mon Sep 17 00:00:00 2001 From: anthdm Date: Sun, 16 Jun 2024 11:18:34 +0200 Subject: [PATCH] integrated auth plugin --- bootstrap/.env.local | 7 +- bootstrap/.gitignore | 1 + bootstrap/app/{ => db}/db.go | 12 +- .../20240610161057_create_users_table.sql | 14 ++ .../20240610163918_add_sessions_table.sql | 14 ++ bootstrap/app/handlers/auth.go | 4 +- bootstrap/app/routes.go | 7 +- bootstrap/app/views/landing/index.templ | 4 +- bootstrap/app/views/layouts/base_layout.templ | 2 +- bootstrap/go.mod | 5 + bootstrap/go.sum | 4 + bootstrap/plugins/auth/auth.templ | 5 +- bootstrap/plugins/auth/auth_handler.go | 11 +- bootstrap/plugins/auth/profile_handler.go | 2 +- bootstrap/plugins/auth/profile_show.templ | 6 +- bootstrap/plugins/auth/signup.templ | 4 +- bootstrap/plugins/auth/types.go | 2 +- bootstrap/public/assets/styles.css | 195 ++++++++++++++++++ kit/kit.go | 4 + 19 files changed, 277 insertions(+), 26 deletions(-) rename bootstrap/app/{ => db}/db.go (63%) create mode 100644 bootstrap/app/db/migrations/20240610161057_create_users_table.sql create mode 100644 bootstrap/app/db/migrations/20240610163918_add_sessions_table.sql diff --git a/bootstrap/.env.local b/bootstrap/.env.local index fdb430b..d857086 100644 --- a/bootstrap/.env.local +++ b/bootstrap/.env.local @@ -20,4 +20,9 @@ MIGRATION_DIR = app/db/migrations # least 32 bytes long. # NOTE: You might want to change this secret when using # your app in production. -APP_SECRET = {{app_secret}} \ No newline at end of file +SUPERKIT_SECRET = {{app_secret}} + +# Authentication Plugin +SUPERKIT_AUTH_REDIRECT_AFTER_LOGIN = /profile +SUPERKIT_AUTH_SESSION_EXPIRY_IN_HOURS = 48 +SUPERKIT_AUTH_SKIP_VERIFY = true diff --git a/bootstrap/.gitignore b/bootstrap/.gitignore index 3089ac0..ecff931 100644 --- a/bootstrap/.gitignore +++ b/bootstrap/.gitignore @@ -4,3 +4,4 @@ bin node_modules tmp .env +app_db \ No newline at end of file diff --git a/bootstrap/app/db.go b/bootstrap/app/db/db.go similarity index 63% rename from bootstrap/app/db.go rename to bootstrap/app/db/db.go index 9b09070..de7695c 100644 --- a/bootstrap/app/db.go +++ b/bootstrap/app/db/db.go @@ -1,4 +1,4 @@ -package app +package db import ( "log" @@ -8,12 +8,16 @@ import ( "github.com/anthdm/gothkit/kit" _ "github.com/mattn/go-sqlite3" + "github.com/uptrace/bun" "github.com/uptrace/bun/dialect/sqlitedialect" "github.com/uptrace/bun/extra/bundebug" ) -var DB *bun.DB +// I could not came up with a better naming for this. +// Ideally, app should export a global variable called "DB" +// but this will cause imports cycles for plugins. +var Query *bun.DB func init() { config := db.Config{ @@ -27,8 +31,8 @@ func init() { if err != nil { log.Fatal(err) } - DB = bun.NewDB(db, sqlitedialect.New()) + Query = bun.NewDB(db, sqlitedialect.New()) if kit.IsDevelopment() { - DB.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) + Query.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) } } diff --git a/bootstrap/app/db/migrations/20240610161057_create_users_table.sql b/bootstrap/app/db/migrations/20240610161057_create_users_table.sql new file mode 100644 index 0000000..cd84d68 --- /dev/null +++ b/bootstrap/app/db/migrations/20240610161057_create_users_table.sql @@ -0,0 +1,14 @@ +-- +goose Up +create table if not exists users( + id integer primary key, + email text unique not null, + password_hash text not null, + first_name text not null, + last_name text not null, + email_verified_at timestamp with time zone, + created_at timestamp with time zone not null, + updated_at timestamp with time zone not null +); + +-- +goose Down +drop table if exists users; diff --git a/bootstrap/app/db/migrations/20240610163918_add_sessions_table.sql b/bootstrap/app/db/migrations/20240610163918_add_sessions_table.sql new file mode 100644 index 0000000..ae082c8 --- /dev/null +++ b/bootstrap/app/db/migrations/20240610163918_add_sessions_table.sql @@ -0,0 +1,14 @@ +-- +goose Up +create table if not exists sessions( + id integer primary key, + token string not null, + user_id integer not null references users, + ip_address text, + user_agent text, + expires_at timestamp with time zone not null, + last_login_at timestamp with time zone, + created_at timestamp with time zone not null +); + +-- +goose Down +drop table if exists sessions; diff --git a/bootstrap/app/handlers/auth.go b/bootstrap/app/handlers/auth.go index dfe47e1..e37a94a 100644 --- a/bootstrap/app/handlers/auth.go +++ b/bootstrap/app/handlers/auth.go @@ -1,13 +1,11 @@ package handlers import ( - "net/http" - "AABBCCDD/app/types" "github.com/anthdm/gothkit/kit" ) -func HandleAuthentication(w http.ResponseWriter, r *http.Request) (kit.Auth, error) { +func HandleAuthentication(kit *kit.Kit) (kit.Auth, error) { return types.AuthUser{}, nil } diff --git a/bootstrap/app/routes.go b/bootstrap/app/routes.go index 06bfb0d..dbbb027 100644 --- a/bootstrap/app/routes.go +++ b/bootstrap/app/routes.go @@ -3,6 +3,7 @@ package app import ( "AABBCCDD/app/handlers" "AABBCCDD/app/views/errors" + "AABBCCDD/plugins/auth" "log/slog" "github.com/anthdm/gothkit/kit" @@ -21,9 +22,11 @@ func InitializeMiddleware(router *chi.Mux) { // Define your routes in here func InitializeRoutes(router *chi.Mux) { - // Comment out to configure your authentication + // Authentication plugin: + auth.InitializeRoutes(router) + authConfig := kit.AuthenticationConfig{ - AuthFunc: handlers.HandleAuthentication, + AuthFunc: auth.AuthenticateUser, RedirectURL: "/login", } diff --git a/bootstrap/app/views/landing/index.templ b/bootstrap/app/views/landing/index.templ index 1a5082c..2b788c0 100644 --- a/bootstrap/app/views/landing/index.templ +++ b/bootstrap/app/views/landing/index.templ @@ -6,9 +6,9 @@ import ( templ Index() { @layouts.App() { -
+
-

superkit

+

superkit

Build high-performance apps swiftly with minimal team resources. diff --git a/bootstrap/app/views/layouts/base_layout.templ b/bootstrap/app/views/layouts/base_layout.templ index f1cabd1..47589ae 100644 --- a/bootstrap/app/views/layouts/base_layout.templ +++ b/bootstrap/app/views/layouts/base_layout.templ @@ -22,7 +22,7 @@ templ BaseLayout() { - + { children... } diff --git a/bootstrap/go.mod b/bootstrap/go.mod index 0783e32..f6c16ed 100644 --- a/bootstrap/go.mod +++ b/bootstrap/go.mod @@ -6,11 +6,13 @@ require ( github.com/a-h/templ v0.2.707 github.com/anthdm/gothkit v0.0.0-20240615140127-d74e729d7c71 github.com/go-chi/chi/v5 v5.0.12 + github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 github.com/mattn/go-sqlite3 v1.14.22 github.com/uptrace/bun v1.2.1 github.com/uptrace/bun/dialect/sqlitedialect v1.2.1 github.com/uptrace/bun/extra/bundebug v1.2.1 + golang.org/x/crypto v0.24.0 ) require ( @@ -25,3 +27,6 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/sys v0.21.0 // indirect ) + +// uncomment for local development on the superkit core. +// replace github.com/anthdm/gothkit => ../ diff --git a/bootstrap/go.sum b/bootstrap/go.sum index 3215512..4f8d808 100644 --- a/bootstrap/go.sum +++ b/bootstrap/go.sum @@ -14,6 +14,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= @@ -47,6 +49,8 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= diff --git a/bootstrap/plugins/auth/auth.templ b/bootstrap/plugins/auth/auth.templ index f3b731d..1510853 100644 --- a/bootstrap/plugins/auth/auth.templ +++ b/bootstrap/plugins/auth/auth.templ @@ -1,9 +1,10 @@ package auth import ( - "auth/app/views/layouts" v "github.com/anthdm/gothkit/validate" - "auth/app/views/components" + + "AABBCCDD/app/views/layouts" + "AABBCCDD/app/views/components" ) type AuthIndexPageData struct { diff --git a/bootstrap/plugins/auth/auth_handler.go b/bootstrap/plugins/auth/auth_handler.go index 0ce45a1..e6babc6 100644 --- a/bootstrap/plugins/auth/auth_handler.go +++ b/bootstrap/plugins/auth/auth_handler.go @@ -1,9 +1,10 @@ package auth import ( - "auth/app/db" + "AABBCCDD/app/db" "cmp" "database/sql" + "fmt" "net/http" "os" "strconv" @@ -69,8 +70,8 @@ func HandleAuthCreate(kit *kit.Kit) error { return kit.Render(LoginForm(values, errors)) } - // todo: use the kit.Getenv instead of the comp thingy - skipVerify := cmp.Or(os.Getenv("SUPERKIT_AUTH_SKIP_VERIFY"), "false") + skipVerify := kit.Getenv("SUPERKIT_AUTH_SKIP_VERIFY", "false") + fmt.Println(skipVerify) if skipVerify != "true" { if user.EmailVerifiedAt.Equal(time.Time{}) { errors.Add("verified", "please verify your email") @@ -78,7 +79,7 @@ func HandleAuthCreate(kit *kit.Kit) error { } } - sessionExpiryStr := os.Getenv("SUPERKIT_AUTH_SESSION_EXPIRY_IN_HOURS") + sessionExpiryStr := kit.Getenv("SUPERKIT_AUTH_SESSION_EXPIRY_IN_HOURS", "48") sessionExpiry, err := strconv.Atoi(sessionExpiryStr) if err != nil { sessionExpiry = 48 @@ -102,7 +103,7 @@ func HandleAuthCreate(kit *kit.Kit) error { sess.Values["sessionToken"] = session.Token sess.Save(kit.Request, kit.Response) - redirectURL := cmp.Or(os.Getenv("SUPERKIT_AUTH_REDIRECT_AFTER_LOGIN"), "/profile") + redirectURL := kit.Getenv("SUPERKIT_AUTH_REDIRECT_AFTER_LOGIN", "/profile") return kit.Redirect(http.StatusSeeOther, redirectURL) } diff --git a/bootstrap/plugins/auth/profile_handler.go b/bootstrap/plugins/auth/profile_handler.go index 136e9de..922802c 100644 --- a/bootstrap/plugins/auth/profile_handler.go +++ b/bootstrap/plugins/auth/profile_handler.go @@ -1,7 +1,7 @@ package auth import ( - "auth/app/db" + "AABBCCDD/app/db" "fmt" "github.com/anthdm/gothkit/kit" diff --git a/bootstrap/plugins/auth/profile_show.templ b/bootstrap/plugins/auth/profile_show.templ index f713b3d..7c7ac63 100644 --- a/bootstrap/plugins/auth/profile_show.templ +++ b/bootstrap/plugins/auth/profile_show.templ @@ -1,9 +1,11 @@ package auth import ( - "auth/app/views/layouts" - v "github.com/anthdm/gothkit/validate" "fmt" + + v "github.com/anthdm/gothkit/validate" + + "AABBCCDD/app/views/layouts" ) templ ProfileShow(formValues ProfileFormValues) { diff --git a/bootstrap/plugins/auth/signup.templ b/bootstrap/plugins/auth/signup.templ index dfb8265..6cc3258 100644 --- a/bootstrap/plugins/auth/signup.templ +++ b/bootstrap/plugins/auth/signup.templ @@ -2,8 +2,8 @@ package auth import ( v "github.com/anthdm/gothkit/validate" - "auth/app/views/layouts" - "auth/app/views/components" + "AABBCCDD/app/views/layouts" + "AABBCCDD/app/views/components" ) type SignupIndexPageData struct { diff --git a/bootstrap/plugins/auth/types.go b/bootstrap/plugins/auth/types.go index d008071..d18ca5e 100644 --- a/bootstrap/plugins/auth/types.go +++ b/bootstrap/plugins/auth/types.go @@ -1,7 +1,7 @@ package auth import ( - "auth/app/db" + "AABBCCDD/app/db" "context" "time" diff --git a/bootstrap/public/assets/styles.css b/bootstrap/public/assets/styles.css index 5babadf..72ce8f1 100644 --- a/bootstrap/public/assets/styles.css +++ b/bootstrap/public/assets/styles.css @@ -625,11 +625,35 @@ body { } } +.fixed { + position: fixed; +} + +.right-6 { + right: 1.5rem; +} + +.top-6 { + top: 1.5rem; +} + .mx-auto { margin-left: auto; margin-right: auto; } +.ml-4 { + margin-left: 1rem; +} + +.mt-10 { + margin-top: 2.5rem; +} + +.mt-32 { + margin-top: 8rem; +} + .inline-block { display: inline-block; } @@ -638,6 +662,14 @@ body { display: flex; } +.inline-flex { + display: inline-flex; +} + +.hidden { + display: none; +} + .h-screen { height: 100vh; } @@ -659,10 +691,22 @@ body { max-width: 80rem; } +.max-w-md { + max-width: 28rem; +} + +.max-w-sm { + max-width: 24rem; +} + .cursor-pointer { cursor: pointer; } +.list-disc { + list-style-type: disc; +} + .flex-col { flex-direction: column; } @@ -679,10 +723,22 @@ body { justify-content: space-between; } +.gap-1 { + gap: 0.25rem; +} + +.gap-10 { + gap: 2.5rem; +} + .gap-12 { gap: 3rem; } +.gap-2 { + gap: 0.5rem; +} + .gap-3 { gap: 0.75rem; } @@ -691,19 +747,45 @@ body { gap: 1rem; } +.gap-6 { + gap: 1.5rem; +} + +.gap-8 { + gap: 2rem; +} + .rounded-md { border-radius: calc(var(--radius) - 2px); } +.border { + border-width: 1px; +} + .border-b { border-bottom-width: 1px; } +.border-input { + --tw-border-opacity: 1; + border-color: hsl(var(--input) / var(--tw-border-opacity)); +} + +.border-red-500 { + --tw-border-opacity: 1; + border-color: rgb(239 68 68 / var(--tw-border-opacity)); +} + .bg-primary { --tw-bg-opacity: 1; background-color: hsl(var(--primary) / var(--tw-bg-opacity)); } +.bg-transparent { + background-color: transparent; +} + .bg-gradient-to-r { background-image: linear-gradient(to right, var(--tw-gradient-stops)); } @@ -728,11 +810,31 @@ body { background-clip: text; } +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + .px-4 { padding-left: 1rem; padding-right: 1rem; } +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + +.py-12 { + padding-top: 3rem; + padding-bottom: 3rem; +} + .py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; @@ -776,6 +878,11 @@ body { line-height: 1.25rem; } +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + .font-bold { font-weight: 700; } @@ -792,6 +899,10 @@ body { text-transform: uppercase; } +.tracking-wide { + letter-spacing: 0.025em; +} + .text-foreground { --tw-text-opacity: 1; color: hsl(var(--foreground) / var(--tw-text-opacity)); @@ -807,17 +918,101 @@ body { color: hsl(var(--primary-foreground) / var(--tw-text-opacity)); } +.text-red-500 { + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity)); +} + .text-transparent { color: transparent; } +.underline { + text-decoration-line: underline; +} + +.shadow-sm { + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.ring-offset-background { + --tw-ring-offset-color: hsl(var(--background) / 1); +} + +.transition-colors { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-200 { + transition-duration: 200ms; +} + /* fix for Alpine users. */ [x-cloak] { display: none !important; } +.placeholder\:text-neutral-500::-moz-placeholder { + --tw-text-opacity: 1; + color: rgb(115 115 115 / var(--tw-text-opacity)); +} + +.placeholder\:text-neutral-500::placeholder { + --tw-text-opacity: 1; + color: rgb(115 115 115 / var(--tw-text-opacity)); +} + +.hover\:bg-primary\/90:hover { + background-color: hsl(var(--primary) / 0.9); +} + +.focus\:border-neutral-300:focus { + --tw-border-opacity: 1; + border-color: rgb(212 212 212 / var(--tw-border-opacity)); +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.focus\:ring:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus\:ring-primary:focus { + --tw-ring-opacity: 1; + --tw-ring-color: hsl(var(--primary) / var(--tw-ring-opacity)); +} + +.disabled\:cursor-not-allowed:disabled { + cursor: not-allowed; +} + +.disabled\:opacity-50:disabled { + opacity: 0.5; +} + @media (min-width: 1024px) { + .lg\:mt-20 { + margin-top: 5rem; + } + + .lg\:mt-40 { + margin-top: 10rem; + } + + .lg\:mt-32 { + margin-top: 8rem; + } + .lg\:text-4xl { font-size: 2.25rem; line-height: 2.5rem; diff --git a/kit/kit.go b/kit/kit.go index 430efcf..c7830f5 100644 --- a/kit/kit.go +++ b/kit/kit.go @@ -92,6 +92,10 @@ func (kit *Kit) Render(c templ.Component) error { return c.Render(kit.Request.Context(), kit.Response) } +func (kit *Kit) Getenv(name string, def string) string { + return Getenv(name, def) +} + func Handler(h HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { kit := &Kit{