Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
f68f002
fix: remove ProjectSectionNavChevron ('>' button) from all project pa…
nazarli-shabnam Apr 4, 2026
1002dfd
feat: implement auth ui pages with better ui(LoginPage,SignUpPage,For…
nazarli-shabnam Apr 4, 2026
9039940
refactor: Update auth, handler, model and 6 related areas for API and ui
nazarli-shabnam Apr 4, 2026
a909850
feat: add password_reset_tokens table with user_id, oken, and expire…
nazarli-shabnam Apr 4, 2026
89140d3
fix: linting
nazarli-shabnam Apr 4, 2026
cc3cc1a
refactor: update api, auth, handler, mail, middleware for API and ui
nazarli-shabnam Apr 5, 2026
f04b46a
feat(auth): add password_reset_tokens table for user password reset f…
nazarli-shabnam Apr 5, 2026
5558a5a
Merge branch '80-fix-create-views-buttons' of https://github.com/Devl…
nazarli-shabnam Apr 5, 2026
7310246
refactor(routing): remove forgot and reset password page routes
nazarli-shabnam Apr 5, 2026
adaef29
refactor: update api, auth, config, handler, model for API and ui
nazarli-shabnam Apr 5, 2026
b066032
feat(db): create accounts table to store user provider connections
nazarli-shabnam Apr 5, 2026
0ab34c3
feat(api): add auth for API
nazarli-shabnam Apr 9, 2026
109febb
feat(deps): add glebarez/sqlite dependency for sqlite database support
nazarli-shabnam Apr 9, 2026
ec83439
feat(auth): add env vars for google oauth and magic code login, updat…
nazarli-shabnam Apr 11, 2026
db325dc
feat(auth): add SignUpMagic and SessionForEmailUser to enable magic l…
nazarli-shabnam Apr 11, 2026
29a019a
refactor(ui): update api, instance-admin, pages, services for ui
nazarli-shabnam Apr 11, 2026
c2caeb8
feat(auth): introduce email magic code login/signup and API public UR…
nazarli-shabnam Apr 11, 2026
f4b111a
feat(auth): add email login magic code support and improve secret dec…
nazarli-shabnam Apr 11, 2026
b60122c
style(login): improve code formatting for readability
nazarli-shabnam Apr 11, 2026
37af3d7
chore(merge): bring sticky-notes updates into forgot-password branch
nazarli-shabnam Apr 14, 2026
51275b0
Merge remote-tracking branch 'origin/main' into forgot-password-in-login
nazarli-shabnam Apr 14, 2026
c36c5da
feat: all three edit pages (Google, GitHub, GitLab
nazarli-shabnam Apr 14, 2026
77d4939
feat: removed OAuthRedirectBase from AuthHandler
nazarli-shabnam Apr 14, 2026
a70ac2d
refactor: update auth, handler, router, contexts for API and ui
nazarli-shabnam Apr 14, 2026
488f8c9
refactor: replace the invite handling + ensureDefaultWorkspace with t…
nazarli-shabnam Apr 14, 2026
9dc0bc1
refactor: update RootRedirect to handle the 'no workspaces' case prop…
nazarli-shabnam Apr 14, 2026
53d50e2
chore: linting checks
nazarli-shabnam Apr 14, 2026
d3a16cf
chore: husky fixture
nazarli-shabnam Apr 14, 2026
361e34f
feat: set-password UI page
nazarli-shabnam Apr 14, 2026
07d2db6
feat: return distinct error for deactivated users
nazarli-shabnam Apr 14, 2026
dd703c9
feat: informational message when SMTP is off
nazarli-shabnam Apr 14, 2026
05716c8
fix: copilot warnings
nazarli-shabnam Apr 15, 2026
9f2ac4b
fix: persist organization size and harden auth migration
nazarli-shabnam Apr 15, 2026
6dd21d0
Merge branch 'main' of https://github.com/Devlaner/devlane into forgo…
nazarli-shabnam Apr 15, 2026
ee3e944
fix: catch block now uses getApiErrorMessage(err)
nazarli-shabnam Apr 15, 2026
e9cdf2e
fix: no longer duplicate the helpers
nazarli-shabnam Apr 15, 2026
496e86f
fix: validated check
nazarli-shabnam Apr 15, 2026
c4d79c5
fix: linting fixed
nazarli-shabnam Apr 15, 2026
aaeacba
fix: run prettier in pre-commit
nazarli-shabnam Apr 15, 2026
13be175
Merge branch 'main' of https://github.com/Devlaner/devlane into forgo…
nazarli-shabnam Apr 15, 2026
43bc47e
fix: set all three explicitly: API_PUBLIC_URL, FRONTEND_PUBLIC_URL, A…
nazarli-shabnam Apr 15, 2026
e284e65
feat: Built from API_PUBLIC_URL, fallback to request host if unset (w…
nazarli-shabnam Apr 15, 2026
9e6e8c5
fix: 000002 = password_reset_tokens, accounts uses access_token_expir…
nazarli-shabnam Apr 15, 2026
43932fe
fix: empty string keeps requests relative in prod
nazarli-shabnam Apr 15, 2026
9ab12ad
fix: magic code vs password login, parallel tests, clipboard
nazarli-shabnam Apr 15, 2026
0eebb99
fix: Lax, not Strict
nazarli-shabnam Apr 15, 2026
6599acc
feat: requires an SMTP host in the instance email settings and return…
nazarli-shabnam Apr 15, 2026
a8e71f3
fix: server signout uses same session source as auth middleware; redu…
nazarli-shabnam Apr 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/workflows/ui-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,19 @@ jobs:
with:
node-version: "22"
cache: npm
cache-dependency-path: ui/package-lock.json
cache-dependency-path: |
package-lock.json
ui/package-lock.json

- name: Install root dependencies (Husky)
working-directory: .
run: npm ci

- name: Verify Husky setup
working-directory: .
run: |
npm run prepare
npx husky --version

- name: Install dependencies
run: npm ci
Expand Down
17 changes: 14 additions & 3 deletions api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,19 @@ REDIS_DB=0
# RabbitMQ
RABBITMQ_URL=amqp://guest:guest@localhost:5672/

# Frontend URL for invite links in emails (e.g. https://app.example.com). If unset, CORS_ORIGIN is used.
APP_BASE_URL=https://app.example.com
# Frontend URL for invite links and post-login redirects. If unset, CORS_ORIGIN is used.
APP_BASE_URL=http://localhost:5173

# Browser-visible SPA origin for OAuth admin hints (Google Authorized JavaScript origins, GitHub Homepage URL). If unset, APP_BASE_URL then CORS_ORIGIN.
FRONTEND_PUBLIC_URL=http://localhost:5173

# Public URL of this API (OAuth redirect URIs must hit the API, not the SPA).
API_PUBLIC_URL=http://localhost:8080

# OAuth credentials (Google, GitHub, GitLab) are configured via Instance Admin UI, not env vars.

# HMAC key for email login codes (set in production).
# MAGIC_CODE_SECRET=

# MinIO (S3-compatible)
MINIO_ENDPOINT=localhost:9000
Expand All @@ -34,4 +45,4 @@ MINIO_USE_SSL=false
MIGRATIONS_PATH=migrations


INSTANCE_ENCRYPTION_KEY=fhFGHFgrey576ytHFDRTy5755rhfhfghfhf
INSTANCE_ENCRYPTION_KEY=change-me-generate-a-random-key
22 changes: 15 additions & 7 deletions api/cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ func main() {
os.Exit(1)
}

sqlDB, _ := db.DB()
sqlDB, err := db.DB()
if err != nil {
log.Error("get underlying sql.DB", "error", err)
os.Exit(1)
}
defer sqlDB.Close()

// Redis
Expand Down Expand Up @@ -80,12 +84,16 @@ func main() {
}

r := router.New(router.Config{
Log: log,
DB: db,
Redis: rdb,
Queue: queuePublisher,
Minio: mc,
CORSAllowOrigin: cfg.CORSAllowOrigin,
Log: log,
DB: db,
Redis: rdb,
Queue: queuePublisher,
Minio: mc,
CORSAllowOrigin: cfg.CORSAllowOrigin,
AppBaseURL: cfg.AppBaseURL,
FrontendPublicURL: cfg.FrontendPublicURL,
APIPublicURL: cfg.APIPublicURL,
MagicCodeSecret: cfg.MagicCodeSecret,
})

// Start task consumer when RabbitMQ is available
Expand Down
7 changes: 7 additions & 0 deletions api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.25.5

require (
github.com/gin-gonic/gin v1.11.0
github.com/glebarez/sqlite v1.11.0
github.com/golang-migrate/migrate/v4 v4.19.1
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
Expand All @@ -24,6 +25,7 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
Expand Down Expand Up @@ -52,6 +54,7 @@ require (
github.com/philhofer/fwd v1.2.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/tinylib/msgp v1.6.1 // indirect
Expand All @@ -67,4 +70,8 @@ require (
golang.org/x/text v0.34.0 // indirect
golang.org/x/tools v0.41.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
)
17 changes: 17 additions & 0 deletions api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
Expand All @@ -70,6 +74,8 @@ github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjY
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
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/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
Expand Down Expand Up @@ -142,6 +148,9 @@ github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzuk
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4=
github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
Expand Down Expand Up @@ -207,3 +216,11 @@ gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
34 changes: 34 additions & 0 deletions api/internal/auth/magic_code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package auth

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strings"
)

// DefaultMagicCodeHMACKey is used when MAGIC_CODE_SECRET is unset (development only).
const DefaultMagicCodeHMACKey = "devlane-insecure-magic-code-hmac-key-change-in-production"

// NormalizeMagicCode strips spaces and hyphens so users can paste formatted codes.
func NormalizeMagicCode(code string) string {
s := strings.TrimSpace(code)
s = strings.ReplaceAll(s, " ", "")
s = strings.ReplaceAll(s, "-", "")
return s
}

// MagicCodeHMAC returns a hex-encoded HMAC-SHA256 of the normalized email and code.
func MagicCodeHMAC(secret, email, code string) string {
e := strings.ToLower(strings.TrimSpace(email))
c := NormalizeMagicCode(code)
key := strings.TrimSpace(secret)
if key == "" {
key = DefaultMagicCodeHMACKey
}
mac := hmac.New(sha256.New, []byte(key))
_, _ = mac.Write([]byte(e))
_, _ = mac.Write([]byte{0})
_, _ = mac.Write([]byte(c))
return hex.EncodeToString(mac.Sum(nil))
}
28 changes: 28 additions & 0 deletions api/internal/auth/magic_code_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package auth

import "testing"

func TestNormalizeMagicCode(t *testing.T) {
t.Parallel()
if got := NormalizeMagicCode(" 123 456 "); got != "123456" {
t.Fatalf("got %q", got)
}
if got := NormalizeMagicCode("12-34-56"); got != "123456" {
t.Fatalf("got %q", got)
}
}

func TestMagicCodeHMAC_Deterministic(t *testing.T) {
t.Parallel()
a := MagicCodeHMAC("secret", "A@B.com", "123456")
b := MagicCodeHMAC("secret", "a@b.com", "123456")
if a != b {
t.Fatalf("email case should not matter")
}
if MagicCodeHMAC("secret", "a@b.com", "123456") != MagicCodeHMAC("secret", "a@b.com", "1234 56") {
t.Fatalf("spacing should not matter")
}
if MagicCodeHMAC("s1", "a@b.com", "123456") == MagicCodeHMAC("s2", "a@b.com", "123456") {
t.Fatalf("secret should matter")
}
}
Loading
Loading