Skip to content

Commit

Permalink
login feature
Browse files Browse the repository at this point in the history
  • Loading branch information
simon-ding committed Jul 12, 2024
1 parent a172ca0 commit 60c3c88
Show file tree
Hide file tree
Showing 16 changed files with 751 additions and 16 deletions.
6 changes: 6 additions & 0 deletions db/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ const (
SettingDownloadDir = "download_dir"
)

const (
SettingAuthEnabled = "auth_enbled"
SettingUsername = "auth_username"
SettingPassword = "auth_password"
)

const (
IndexerTorznabImpl = "torznab"
)
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ require (
golang.org/x/net v0.25.0
)

require github.com/golang-jwt/jwt/v5 v5.2.1 // indirect

require (
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 // indirect
github.com/agext/levenshtein v1.2.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
Expand Down
17 changes: 16 additions & 1 deletion pkg/utils/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package utils

import "unicode"
import (
"unicode"

"golang.org/x/crypto/bcrypt"
)

func isASCII(s string) bool {
for _, c := range s {
Expand All @@ -11,3 +15,14 @@ func isASCII(s string) bool {
return true
}

// HashPassword generates a bcrypt hash for the given password.
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err
}

// VerifyPassword verifies if the given password matches the stored hash.
func VerifyPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
141 changes: 141 additions & 0 deletions server/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package server

import (
"net/http"
"polaris/db"
"polaris/log"
"polaris/pkg/utils"
"strings"
"time"

"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"github.com/pkg/errors"
)

func (s *Server) isAuthEnabled() bool {
authEnabled := s.db.GetSetting(db.SettingAuthEnabled)
return authEnabled == "true"
}

func (s *Server) authModdleware(c *gin.Context) {
if !s.isAuthEnabled() {
c.Next()
return
}

auth := c.GetHeader("Authorization")
if auth == "" {
c.AbortWithStatus(http.StatusForbidden)
return
}
auth = strings.TrimPrefix(auth, "Bearer ")
log.Infof("current token: %v", auth)
token, err := jwt.ParseWithClaims(auth, &jwt.RegisteredClaims{}, func(t *jwt.Token) (interface{}, error) {
return []byte(secretKey), nil
})
if err != nil {
log.Errorf("parse token error: %v", err)
c.AbortWithStatus(http.StatusForbidden)
return
}
if !token.Valid {
log.Errorf("token is not valid: %v", auth)
c.AbortWithStatus(http.StatusForbidden)
return
}
claim := token.Claims.(*jwt.RegisteredClaims)

if time.Until(claim.ExpiresAt.Time) <= 0 {
log.Infof("token is no longer valid: %s", auth)
c.AbortWithStatus(http.StatusForbidden)
return
}
c.Next()

}

type LoginIn struct {
User string `json:"user"`
Password string `json:"password"`
}

const secretKey = "r1OF7nhpNjnYiGKtTLuKEVq7YznzT"

func (s *Server) Login(c *gin.Context) (interface{}, error) {
var in LoginIn

if err := c.ShouldBindJSON(&in); err != nil {
return nil, errors.Wrap(err, "bind json")
}

if !s.isAuthEnabled() {
return nil, nil
}

user := s.db.GetSetting(db.SettingUsername)
if user != in.User {
return nil, errors.New("login fail")
}
password := s.db.GetSetting(db.SettingPassword)
if !utils.VerifyPassword(in.Password, password) {
return nil, errors.New("login fail")
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
Issuer: "system",
Subject: in.User,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
})
sig, err := token.SignedString([]byte(secretKey))
if err != nil {
return nil, errors.Wrap(err, "sign")
}
return gin.H{
"token": sig,
}, nil
}

type EnableAuthIn struct {
Enable bool `json:"enable"`
User string `json:"user"`
Password string `json:"password"`
}

func (s *Server) EnableAuth(c *gin.Context) (interface{}, error) {
var in EnableAuthIn
if err := c.ShouldBindJSON(&in); err != nil {
return nil, errors.Wrap(err, "bind json")
}

if in.Enable && (in.User == "" || in.Password == "") {
return nil, errors.New("user password should not empty")
}
if !in.Enable {
log.Infof("disable auth")
s.db.SetSetting(db.SettingAuthEnabled, "false")
} else {
log.Info("enable auth")
s.db.SetSetting(db.SettingAuthEnabled, "true")
s.db.SetSetting(db.SettingUsername, in.User)

hash, err := utils.HashPassword(in.Password)
if err != nil {
return nil, errors.Wrap(err, "hash password")
}
s.db.SetSetting(db.SettingPassword, hash)
}
return "success", nil
}

func (s *Server) GetAuthSetting(c *gin.Context) (interface{}, error) {
enabled := s.db.GetSetting(db.SettingAuthEnabled)
user := s.db.GetSetting(db.SettingUsername)

return EnableAuthIn{
Enable: enabled == "true",
User: user,
}, nil
}
5 changes: 5 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,17 @@ func (s *Server) Serve() error {
//st, _ := fs.Sub(ui.Web, "build/web")
s.r.Use(static.Serve("/", static.EmbedFolder(ui.Web, "build/web")))

s.r.POST("/api/login", HttpHandler(s.Login))

api := s.r.Group("/api/v1")
api.Use(s.authModdleware)

setting := api.Group("/setting")
{
setting.POST("/do", HttpHandler(s.SetSetting))
setting.GET("/do", HttpHandler(s.GetSetting))
setting.POST("/auth", HttpHandler(s.EnableAuth))
setting.GET("/auth", HttpHandler(s.GetAuthSetting))
}

tv := api.Group("/tv")
Expand Down
82 changes: 82 additions & 0 deletions ui/lib/login_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
import 'package:flutter_login/flutter_login.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:quiver/strings.dart';
import 'package:ui/providers/login.dart';
import 'package:ui/weclome.dart';

class LoginScreen extends ConsumerWidget {
static const route = '/login';

const LoginScreen({super.key});

Duration get loginTime => Duration(milliseconds: timeDilation.ceil() * 2250);


Future<String?> _recoverPassword(String name) {
return Future.delayed(loginTime).then((_) {
return null;
});
}


@override
Widget build(BuildContext context, WidgetRef ref) {
return FlutterLogin(
title: 'Polaris',
onLogin: (data) {
ref.read(authSettingProvider.notifier).login(data.name, data.password);
},
onSubmitAnimationCompleted: () {
context.go(WelcomePage.route);
},
onRecoverPassword: _recoverPassword,
userValidator: (value) => isBlank(value)? "不能为空":null,
userType: LoginUserType.name,
hideForgotPasswordButton: true,
messages: LoginMessages(
userHint: '用户名',
passwordHint: '密码',
loginButton: '登录',
),
);
}
}

class IntroWidget extends StatelessWidget {
const IntroWidget({super.key});

@override
Widget build(BuildContext context) {
return const Column(
children: [
Text.rich(
TextSpan(
children: [
TextSpan(
text: "You are trying to login/sign up on server hosted on ",
),
TextSpan(
text: "example.com",
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
textAlign: TextAlign.justify,
),
Row(
children: <Widget>[
Expanded(child: Divider()),
Padding(
padding: EdgeInsets.all(8.0),
child: Text("Authenticate"),
),
Expanded(child: Divider()),
],
),
],
);
}
}
10 changes: 8 additions & 2 deletions ui/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:ui/login_page.dart';
import 'package:ui/navdrawer.dart';
import 'package:ui/providers/APIs.dart';
import 'package:ui/search.dart';
import 'package:ui/system_settings.dart';
import 'package:ui/tv_details.dart';
Expand Down Expand Up @@ -74,15 +76,19 @@ class MyApp extends StatelessWidget {
path: TvDetailsPage.route,
builder: (context, state) =>
TvDetailsPage(seriesId: state.pathParameters['id']!),
)
),
],
);

final _router = GoRouter(
navigatorKey: _rootNavigatorKey,
navigatorKey: APIs.navigatorKey,
initialLocation: WelcomePage.route,
routes: [
_shellRoute,
GoRoute(
path: LoginScreen.route,
builder: (context, state) =>const LoginScreen(),
)
],
);

Expand Down
38 changes: 38 additions & 0 deletions ui/lib/providers/APIs.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';

class APIs {
static final _baseUrl = baseUrl();
Expand All @@ -14,16 +18,50 @@ class APIs {
static final addDownloadClientUrl = "$_baseUrl/api/v1/downloader/add";
static final delDownloadClientUrl = "$_baseUrl/api/v1/downloader/del/";
static final storageUrl = "$_baseUrl/api/v1/storage/";
static final loginUrl = "$_baseUrl/api/login";
static final loginSettingUrl = "$_baseUrl/api/v1/setting/auth";

static const tmdbImgBaseUrl = "https://image.tmdb.org/t/p/w500/";

static const tmdbApiKey = "tmdb_api_key";
static const downloadDirKey = "download_dir";

static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();

static String baseUrl() {
if (kReleaseMode) {
return "";
}
return "http://127.0.0.1:8080";
}

static Dio? dio1;

static Future<Dio> getDio() async {
if (dio1 != null) {
return dio1!;
}
final SharedPreferences prefs = await SharedPreferences.getInstance();
var token = prefs.getString("token");
var dio = Dio();
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
options.headers['Authorization'] = "Bearer $token";
return handler.next(options);
},
onError: (error, handler) {
if (error.response?.statusCode != null &&
error.response?.statusCode! == 403) {
final context = navigatorKey.currentContext;
if (context != null) {
context.go('/login');
}
}
return handler.next(error);
},
));
dio1 = dio;
return dio;
}
}
Loading

0 comments on commit 60c3c88

Please sign in to comment.