feat(auth): implement user and device authentication
This commit is contained in:
@@ -15,10 +15,14 @@ import (
|
||||
"cmroubao/backend-api/internal/platform/assetstore"
|
||||
"cmroubao/backend-api/internal/platform/database"
|
||||
"cmroubao/backend-api/internal/platform/migration"
|
||||
"cmroubao/backend-api/internal/platform/password"
|
||||
repository "cmroubao/backend-api/internal/repository/sqlite"
|
||||
"cmroubao/backend-api/internal/transport/authcommon"
|
||||
"cmroubao/backend-api/internal/transport/httpapi"
|
||||
"cmroubao/backend-api/internal/transport/webui"
|
||||
"cmroubao/backend-api/internal/usecase"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -67,6 +71,13 @@ func run() error {
|
||||
|
||||
serverErrors := make(chan error, 1)
|
||||
go func() {
|
||||
if cfg.TLSCertificate != "" {
|
||||
serverErrors <- server.ListenAndServeTLS(
|
||||
cfg.TLSCertificate,
|
||||
cfg.TLSPrivateKey,
|
||||
)
|
||||
return
|
||||
}
|
||||
serverErrors <- server.ListenAndServe()
|
||||
}()
|
||||
|
||||
@@ -158,6 +169,20 @@ func buildRouter(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
passwords, err := password.NewBcrypt(12)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auth, err := usecase.NewAuthService(
|
||||
store,
|
||||
passwords,
|
||||
clock,
|
||||
ids,
|
||||
usecase.CryptoTokenGenerator{},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
webService, err := webui.NewUsecaseAdapter(tasks, assets)
|
||||
if err != nil {
|
||||
@@ -171,6 +196,33 @@ func buildRouter(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authWebService, err := webui.NewAuthUsecaseAdapter(auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
loginLimiter, err := authcommon.NewAttemptLimiter(
|
||||
10,
|
||||
5*time.Minute,
|
||||
4096,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authWebHandler, err := webui.NewAuthHandler(
|
||||
authWebService,
|
||||
renderer,
|
||||
loginLimiter,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
registerPublicAuth, err := httpapi.NewPublicAuthRegistrar(
|
||||
auth,
|
||||
loginLimiter,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
registerAdminRoutes, err := httpapi.NewAdminRouteRegistrar(
|
||||
httpapi.AdminServices{
|
||||
Assets: assets,
|
||||
@@ -181,9 +233,22 @@ func buildRouter(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
registerProtectedRoutes := func(routes gin.IRoutes) error {
|
||||
if err := registerAdminRoutes(routes); err != nil {
|
||||
return err
|
||||
}
|
||||
authWebHandler.RegisterProtected(routes)
|
||||
return nil
|
||||
}
|
||||
return httpapi.NewRouter(httpapi.RouterDependencies{
|
||||
Database: db,
|
||||
RegisterAdminRoutes: registerAdminRoutes,
|
||||
Database: db,
|
||||
RegisterPublicRoutes: func(routes gin.IRoutes) error {
|
||||
webHandler.RegisterStatic(routes)
|
||||
authWebHandler.RegisterPublic(routes)
|
||||
return registerPublicAuth(routes)
|
||||
},
|
||||
RegisterAdminRoutes: registerProtectedRoutes,
|
||||
AdminSessions: auth,
|
||||
LogEvent: func(event string) {
|
||||
log.Print(event)
|
||||
},
|
||||
|
||||
@@ -5,9 +5,13 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cmroubao/backend-api/internal/config"
|
||||
"cmroubao/backend-api/internal/platform/database"
|
||||
"cmroubao/backend-api/internal/platform/migration"
|
||||
)
|
||||
|
||||
@@ -114,6 +118,45 @@ func TestRequireCurrentMigrationsHidesStatusFailure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildRouterRegistersProtectedLogoutRoute(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
db, err := database.Open(
|
||||
ctx,
|
||||
filepath.Join(t.TempDir(), "api.db"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("database.Open() error = %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
runner, err := migration.New(db)
|
||||
if err != nil {
|
||||
t.Fatalf("migration.New() error = %v", err)
|
||||
}
|
||||
if _, err := runner.Up(ctx); err != nil {
|
||||
t.Fatalf("migration.Up() error = %v", err)
|
||||
}
|
||||
router, err := buildRouter(ctx, config.Config{
|
||||
AssetDirectory: filepath.Join(t.TempDir(), "assets"),
|
||||
}, db)
|
||||
if err != nil {
|
||||
t.Fatalf("buildRouter() error = %v", err)
|
||||
}
|
||||
request := httptest.NewRequest(http.MethodPost, "/logout", nil)
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(response, request)
|
||||
|
||||
if response.Code != http.StatusSeeOther ||
|
||||
response.Header().Get("Location") !=
|
||||
"/login?next=%2Ftasks" {
|
||||
t.Fatalf(
|
||||
"status/location = %d / %q",
|
||||
response.Code,
|
||||
response.Header().Get("Location"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type stubMigrationStatusReader struct {
|
||||
statuses []migration.Status
|
||||
err error
|
||||
|
||||
Reference in New Issue
Block a user