Reverse Proxy with SSL support, Generated client Configs, JWT client to server auth, closes #13
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -18,4 +18,6 @@ logs/server.log
|
||||
.goreleaser.yml
|
||||
config.toml.backup
|
||||
/public/static/js/kickwebsocket.js.backup
|
||||
/public/static/js/kickwebsocket-generated.js
|
||||
clientAuth.txt
|
||||
dist
|
@@ -39,6 +39,12 @@ Image of the frontend UI
|
||||
|
||||
- [X] Add torrents from watch folder (cron job every 5 minutes)
|
||||
|
||||
- [X] Authentication from client to server (done via JWT, will add functionality for 3rd party clients later)
|
||||
|
||||
- [X] Reverse Proxy Support with SSL upgrade added (with provided config for nginx)
|
||||
|
||||
- [X] Mostly generated client config from toml.config on first run
|
||||
|
||||
- [ ] Unit testing completed for a large portion of the package
|
||||
|
||||
- [ ] Stability/bug fixing/Optimization rewrite of some of the core structures of the WebUI and base server
|
||||
@@ -49,7 +55,6 @@ Image of the frontend UI
|
||||
|
||||
- [ ] Ability to view TOML settings from WebUI (and perhaps change a few as well)
|
||||
|
||||
- [ ] Authentication from client to server
|
||||
|
||||
- Late 2018
|
||||
|
||||
|
26
config.toml
26
config.toml
@@ -1,12 +1,12 @@
|
||||
|
||||
[serverConfig]
|
||||
|
||||
ServerPort = ":8000" #leave format as is it expects a string with colon
|
||||
ServerAddr = "" #blank will bind to default IP address, usually fine to leave be
|
||||
LogLevel = "Warn" # Options = Debug, Info, Warn, Error, Fatal, Panic
|
||||
LogOutput = "file" #Options = file, stdout #file will print it to logs/server.log
|
||||
LogLevel = "Debug" # Options = Debug, Info, Warn, Error, Fatal, Panic
|
||||
LogOutput = "stdout" #Options = file, stdout #file will print it to logs/server.log
|
||||
|
||||
SeedRatioStop = 1.50 #automatically stops the torrent after it reaches this seeding ratio
|
||||
|
||||
#Relative or absolute path accepted, the server will convert any relative path to an absolute path.
|
||||
DefaultMoveFolder = 'downloaded' #default path that a finished torrent is symlinked to after completion. Torrents added via RSS will default here
|
||||
TorrentWatchFolder = 'torrentUpload' #folder path that is watched for .torrent files and adds them automatically every 5 minutes
|
||||
@@ -17,18 +17,30 @@
|
||||
DownloadRateLimit = "Unlimited"
|
||||
|
||||
|
||||
|
||||
[notifications]
|
||||
|
||||
PushBulletToken = "" #add your pushbullet api token here to notify of torrent completion to pushbullet
|
||||
|
||||
|
||||
[goTorrentWebUI]
|
||||
#Here you can set a basic HTTP login set of credentials
|
||||
WebUIAuth = true # bool, if false no authentication is required for the webUI
|
||||
WebUIUser = "admin"
|
||||
WebUIPassword = "Password"
|
||||
|
||||
|
||||
[reverseProxy]
|
||||
#This is for setting up goTorrent behind a reverse Proxy (with SSL, reverse proxy with no SSL will require editing the WSS connection to a WS connection manually)
|
||||
ProxyEnabled = false #bool, either false or true
|
||||
BaseURL = "yoursubdomain.domain.org/subroute/" # MUST be in the format (if you have a subdomain, and must have trailing slash) "yoursubdomain.domain.org/subroute/"
|
||||
|
||||
[EncryptionPolicy]
|
||||
|
||||
DisableEncryption = false
|
||||
ForceEncryption = false
|
||||
PreferNoEncryption = true
|
||||
|
||||
|
||||
[torrentClientConfig]
|
||||
DownloadDir = 'downloading' #the full OR relative path where the torrent server stores in-progress torrents
|
||||
|
||||
@@ -37,6 +49,9 @@
|
||||
# Never send chunks to peers.
|
||||
NoUpload = false #boolean
|
||||
|
||||
#User-provided Client peer ID. If not present, one is generated automatically.
|
||||
PeerID = "" #string
|
||||
|
||||
#The address to listen for new uTP and TCP bittorrent protocol connections. DHT shares a UDP socket with uTP unless configured otherwise.
|
||||
ListenAddr = "" #Leave Blank for default, syntax "HOST:PORT"
|
||||
|
||||
@@ -48,9 +63,6 @@
|
||||
# Don't create a DHT.
|
||||
NoDHT = false #boolean
|
||||
|
||||
#User-provided Client peer ID. If not present, one is generated automatically.
|
||||
PeerID = "" #string
|
||||
|
||||
#For the bittorrent protocol.
|
||||
DisableUTP = false #bool
|
||||
|
||||
|
12
dist-specific-files/Linux-systemd/ReverseProxy/nginx.conf
Normal file
12
dist-specific-files/Linux-systemd/ReverseProxy/nginx.conf
Normal file
@@ -0,0 +1,12 @@
|
||||
location ^~ /gotorrent/ {
|
||||
proxy_pass http://192.168.1.100:8000/;
|
||||
proxy_redirect http:// https://;
|
||||
proxy_pass_header Server;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $http_address;
|
||||
proxy_set_header X-Scheme $scheme;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
|
||||
}
|
60
engine/authentication_helper.go
Normal file
60
engine/authentication_helper.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type AuthRequest struct {
|
||||
MessageType string `json:"MessageType"`
|
||||
AuthString string `json:"AuthString"`
|
||||
}
|
||||
|
||||
//GoTorrentClaims stores the name of the client (usually user entered) and any standard jwt claims we want to define
|
||||
type GoTorrentClaims struct {
|
||||
ClientName string `json:"clientName"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
//GenerateToken creates a signed token for a client to use to communicate with the server
|
||||
func GenerateToken(claims GoTorrentClaims, signingKey []byte) string {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
signedString, err := token.SignedString(signingKey)
|
||||
if err != nil {
|
||||
Logger.WithFields(logrus.Fields{"error": err}).Fatal("Error signing authentication Token!")
|
||||
}
|
||||
return signedString
|
||||
|
||||
}
|
||||
|
||||
//GenerateSigningKey creates a random key that will be used for JSON Web Token authentication
|
||||
func GenerateSigningKey() []byte {
|
||||
keyString, err := generateRandomASCIIString(24)
|
||||
key := []byte(keyString)
|
||||
if err != nil {
|
||||
Logger.WithFields(logrus.Fields{"error": err}).Fatal("Error generating signing key!")
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func generateRandomASCIIString(length int) (string, error) {
|
||||
result := ""
|
||||
for {
|
||||
if len(result) >= length {
|
||||
return result, nil
|
||||
}
|
||||
num, err := rand.Int(rand.Reader, big.NewInt(int64(127)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
n := num.Int64()
|
||||
// Make sure that the number/byte/letter is inside
|
||||
// the range of printable ASCII characters (excluding space and DEL)
|
||||
if n > 32 && n < 127 {
|
||||
result += string(n)
|
||||
}
|
||||
}
|
||||
}
|
50
engine/clientConnectGenerate.go
Normal file
50
engine/clientConnectGenerate.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
baseFile = `
|
||||
var authMessage = {
|
||||
MessageType: "authRequest",
|
||||
Payload: [ClientAuthString]
|
||||
}
|
||||
|
||||
var kickStart = {
|
||||
MessageType: "torrentListRequest"
|
||||
}
|
||||
|
||||
ws.onopen = function()
|
||||
{
|
||||
ws.send(JSON.stringify(authMessage));
|
||||
console.log("Sending authentication message...", JSON.stringify(authMessage))
|
||||
ws.send(JSON.stringify(kickStart)); //sending out the first ping
|
||||
console.log("Kicking off websocket to server.....", JSON.stringify(kickStart))
|
||||
};`
|
||||
)
|
||||
|
||||
//GenerateClientConfigFile runs at first run (no db client detected) to generate a js file for connecting
|
||||
func GenerateClientConfigFile(config FullClientSettings, authString string) {
|
||||
os.Remove("public/static/js/kickwebsocket-generated.js")
|
||||
var clientFile string
|
||||
if config.UseProxy {
|
||||
clientFile = `
|
||||
ClientAuthString = "` + authString + `"
|
||||
|
||||
var ws = new WebSocket("wss://` + config.BaseURL + `websocket")
|
||||
` + baseFile
|
||||
} else {
|
||||
clientFile = `
|
||||
IP = "` + config.HTTPAddrIP + `"
|
||||
Port = "` + config.WebsocketClientPort + `"
|
||||
ClientAuthString = "` + authString + `"
|
||||
|
||||
var ws = new WebSocket(` + "`" + `ws://${IP}:${Port}/websocket` + "`" + `); //creating websocket
|
||||
` + baseFile
|
||||
|
||||
}
|
||||
clientFileBytes := []byte(clientFile)
|
||||
ioutil.WriteFile("public/static/js/kickwebsocket-generated.js", clientFileBytes, 0755)
|
||||
}
|
@@ -41,7 +41,7 @@ type RSSFeedsNames struct {
|
||||
}
|
||||
|
||||
//TorrentList struct contains the torrent list that is sent to the client
|
||||
type TorrentList struct { //helps create the JSON structure that react expects to recieve
|
||||
type TorrentList struct { //helps create the JSON structure that react expects to receive
|
||||
MessageType string `json:"MessageType"`
|
||||
Totaltorrents int `json:"total"`
|
||||
ClientDBstruct []ClientDB `json:"data"`
|
||||
|
@@ -3,6 +3,7 @@ package engine
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
@@ -17,6 +18,10 @@ type FullClientSettings struct {
|
||||
LoggingLevel logrus.Level
|
||||
LoggingOutput string
|
||||
HTTPAddr string
|
||||
HTTPAddrIP string
|
||||
UseProxy bool
|
||||
WebsocketClientPort string
|
||||
BaseURL string
|
||||
Version int
|
||||
TorrentConfig torrent.Config
|
||||
TFileUploadFolder string
|
||||
@@ -98,9 +103,17 @@ func FullClientSettingsNew() FullClientSettings {
|
||||
}
|
||||
|
||||
var httpAddr string
|
||||
var baseURL string
|
||||
var websocketClientPort string
|
||||
|
||||
httpAddrIP := viper.GetString("serverConfig.ServerAddr")
|
||||
httpAddrPort := viper.GetString("serverConfig.ServerPort")
|
||||
proxySet := viper.GetBool("reverseProxy.ProxyEnabled")
|
||||
websocketClientPort = strings.TrimLeft(viper.GetString("serverConfig.ServerPort"), ":") //Trimming off the colon in front of the port
|
||||
if proxySet {
|
||||
baseURL = viper.GetString("reverseProxy.BaseURL")
|
||||
fmt.Println("WebsocketClientPort", viper.GetString("serverConfig.ServerPort"))
|
||||
}
|
||||
seedRatioStop := viper.GetFloat64("serverConfig.SeedRatioStop")
|
||||
httpAddr = httpAddrIP + httpAddrPort
|
||||
pushBulletToken := viper.GetString("notifications.PushBulletToken")
|
||||
@@ -196,7 +209,11 @@ func FullClientSettingsNew() FullClientSettings {
|
||||
LoggingOutput: logOutput,
|
||||
SeedRatioStop: seedRatioStop,
|
||||
HTTPAddr: httpAddr,
|
||||
HTTPAddrIP: httpAddrIP,
|
||||
UseProxy: proxySet,
|
||||
WebsocketClientPort: websocketClientPort,
|
||||
TorrentConfig: tConfig,
|
||||
BaseURL: baseURL,
|
||||
TFileUploadFolder: "uploadedTorrents",
|
||||
PushBulletToken: pushBulletToken,
|
||||
DefaultMoveFolder: defaultMoveFolderAbs,
|
||||
|
@@ -18,6 +18,8 @@ import LeftMenu from './leftMenu/leftMenu';
|
||||
import TorrentList from './torrentlist';
|
||||
//Notification Element
|
||||
import Notifications from './notifications';
|
||||
//Login Box
|
||||
import Login from './login';
|
||||
|
||||
|
||||
|
||||
@@ -65,6 +67,7 @@ class BasicLayout extends React.PureComponent {
|
||||
|
||||
render() {
|
||||
return [
|
||||
<Login />,
|
||||
<Notifications />,
|
||||
<ReactGridLayout layout={this.state.layout} onLayoutChange={this.onLayoutChange}
|
||||
{...this.props}>
|
||||
|
113
goTorrentWebUI/src/login.js
Normal file
113
goTorrentWebUI/src/login.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Button from 'material-ui/Button';
|
||||
import TextField from 'material-ui/TextField';
|
||||
import { withStyles } from 'material-ui/styles';
|
||||
import PropTypes from 'prop-types';
|
||||
import Dialog, {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
} from 'material-ui/Dialog';
|
||||
import InsertLinkIcon from 'material-ui-icons/Link';
|
||||
import ReactTooltip from 'react-tooltip'
|
||||
import Icon from 'material-ui/Icon';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
|
||||
|
||||
let Loggedin = false
|
||||
|
||||
const button = {
|
||||
fontSize: '60px',
|
||||
marginRight: '20px',
|
||||
}
|
||||
|
||||
const inlineStyle = {
|
||||
display: 'inline-block',
|
||||
backdrop: 'static',
|
||||
}
|
||||
|
||||
const errorStyle = {
|
||||
color: 'red',
|
||||
}
|
||||
|
||||
export default class Login extends React.Component {
|
||||
|
||||
state = {
|
||||
open: false,
|
||||
username: "",
|
||||
password: "",
|
||||
wrongPasswordMessage: "",
|
||||
|
||||
};
|
||||
|
||||
|
||||
componentWillMount = () => {
|
||||
if ((LoginRequired) && (Loggedin == false)) {
|
||||
this.setState({open: true})
|
||||
Loggedin = true
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
//this.setState({ open: false });
|
||||
//let magnetLinkSubmit = this.state.textValue;
|
||||
console.log("Attempting authentication")
|
||||
if ((this.state.username == ClientUsername) && (this.state.password == ClientPassword)) {
|
||||
this.setState({ open: false, username: "", password: "" });
|
||||
} else {
|
||||
this.setState({wrongPasswordMessage: "Wrong Username/Password!", username: "", password: "" })
|
||||
}
|
||||
//this.setState({magnetLinkValue: ""}, {torrentLabel: ""}, {storageValue: ``})
|
||||
}
|
||||
|
||||
handleRequestClose = () => {
|
||||
ws.close()
|
||||
}
|
||||
|
||||
setUserNameValue = (event) => {
|
||||
this.setState({username: event.target.value});
|
||||
}
|
||||
|
||||
setPasswordValue = (event) => {
|
||||
this.setState({password: event.target.value})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, onRequestClose, handleRequestClose, handleSubmit } = this.props;
|
||||
return (
|
||||
<Dialog open={this.state.open} onRequestClose={this.handleRequestClose} disableBackdropClick={true} disableEscapeKeyDown={true} hideBackdrop={true} fullScreen>
|
||||
<DialogTitle>Login Here</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Enter a username and password to connect
|
||||
</DialogContentText>
|
||||
<br />
|
||||
<DialogContentText style={errorStyle}>
|
||||
{this.state.wrongPasswordMessage}
|
||||
</DialogContentText>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
id="name"
|
||||
label="User Name"
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
fullWidth
|
||||
onChange={this.setUserNameValue}
|
||||
/>
|
||||
<TextField id="password" type="password" label="Password" placeholder="Password" fullWidth onChange={this.setPasswordValue} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleRequestClose} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={this.handleSubmit} color="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
};
|
93
main.go
93
main.go
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -18,6 +17,8 @@ import (
|
||||
"github.com/asdine/storm"
|
||||
Engine "github.com/deranjer/goTorrent/engine"
|
||||
Storage "github.com/deranjer/goTorrent/storage"
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/mmcdole/gofeed"
|
||||
@@ -36,6 +37,7 @@ type SingleRSSFeedMessage struct { //TODO had issues with getting this to work w
|
||||
var (
|
||||
//Logger does logging for the entire project
|
||||
Logger = logrus.New()
|
||||
Authenticated = false //to verify if user is authenticated, this is stored here
|
||||
APP_ID = os.Getenv("APP_ID")
|
||||
)
|
||||
|
||||
@@ -49,6 +51,34 @@ func serveHome(w http.ResponseWriter, r *http.Request) {
|
||||
s1.ExecuteTemplate(w, "base", map[string]string{"APP_ID": APP_ID})
|
||||
}
|
||||
|
||||
func handleAuthentication(conn *websocket.Conn, db *storm.DB) {
|
||||
msg := Engine.Message{}
|
||||
err := conn.ReadJSON(&msg)
|
||||
if err != nil {
|
||||
Logger.WithFields(logrus.Fields{"error": err, "SuppliedToken": msg.Payload[0]}).Error("Unable to read authentication message")
|
||||
}
|
||||
authString := msg.Payload[0] //First element will be the auth request
|
||||
fmt.Println("Authstring", authString)
|
||||
signingKeyStruct := Storage.FetchJWTTokens(db)
|
||||
singingKey := signingKeyStruct.SigningKey
|
||||
token, err := jwt.Parse(authString, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return singingKey, nil
|
||||
})
|
||||
if err != nil {
|
||||
Logger.WithFields(logrus.Fields{"error": err, "SuppliedToken": token}).Error("Unable to parse token!")
|
||||
conn.Close()
|
||||
}
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||
fmt.Println("Claims", claims["ClientName"], claims["Issuer"])
|
||||
Authenticated = true
|
||||
} else {
|
||||
Logger.WithFields(logrus.Fields{"error": err}).Error("Authentication Error occured, cannot complete!")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
Engine.Logger = Logger //Injecting the logger into all the packages
|
||||
Storage.Logger = Logger
|
||||
@@ -92,6 +122,34 @@ func main() {
|
||||
}
|
||||
defer db.Close() //defering closing the database until the program closes
|
||||
|
||||
tokens := Storage.IssuedTokensList{} //if first run setting up the authentication tokens
|
||||
err = db.One("ID", 3, &tokens)
|
||||
if err != nil {
|
||||
Logger.WithFields(logrus.Fields{"RSSFeedStore": tokens, "error": err}).Info("No Tokens database found, assuming first run, generating token...")
|
||||
fmt.Println("Error", err)
|
||||
fmt.Println("MAIN TOKEN: %+v\n", tokens)
|
||||
tokens.ID = 3 //creating the initial store
|
||||
claims := Engine.GoTorrentClaims{
|
||||
"goTorrentWebUI",
|
||||
jwt.StandardClaims{
|
||||
Issuer: "goTorrentServer",
|
||||
},
|
||||
}
|
||||
signingkey := Engine.GenerateSigningKey() //Running this will invalidate any certs you already issued!!
|
||||
fmt.Println("SigningKey", signingkey)
|
||||
authString := Engine.GenerateToken(claims, signingkey)
|
||||
tokens.SigningKey = signingkey
|
||||
fmt.Println("ClientToken: ", authString)
|
||||
Engine.GenerateClientConfigFile(Config, authString) //if first run generate the client config file
|
||||
|
||||
tokens.TokenNames = append(tokens.TokenNames, Storage.SingleToken{"firstClient"})
|
||||
err := ioutil.WriteFile("clientAuth.txt", []byte(authString), 0755)
|
||||
if err != nil {
|
||||
Logger.WithFields(logrus.Fields{"error": err}).Warn("Unable to write client auth to file..")
|
||||
}
|
||||
db.Save(&tokens) //Writing all of that to the database
|
||||
}
|
||||
|
||||
cronEngine := Engine.InitializeCronEngine() //Starting the cron engine for tasks
|
||||
Logger.Debug("Cron Engine Initialized...")
|
||||
|
||||
@@ -113,10 +171,12 @@ func main() {
|
||||
Engine.RefreshRSSCron(cronEngine, db, tclient, torrentLocalStorage, Config) // Refresing the RSS feeds on an hourly basis to add torrents that show up in the RSS feed
|
||||
|
||||
router := mux.NewRouter() //setting up the handler for the web backend
|
||||
//reverseProxy := handlers.ProxyHeaders(router) //handlers.ProxyHeaders(router) //TODO pull this from the config file
|
||||
router.HandleFunc("/", serveHome) //Serving the main page for our SPA
|
||||
http.Handle("/static/", http.FileServer(http.Dir("public")))
|
||||
//http.Handle("/static/", http.FileServer(http.Dir("public")))
|
||||
router.PathPrefix("/static/").Handler(http.FileServer(http.Dir("public")))
|
||||
http.Handle("/", router)
|
||||
http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) { //exposing the data to the
|
||||
router.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) { //exposing the data to the
|
||||
TorrentLocalArray = Storage.FetchAllStoredTorrents(db)
|
||||
RunningTorrentArray = Engine.CreateRunningTorrentArray(tclient, TorrentLocalArray, PreviousTorrentArray, Config, db) //Updates the RunningTorrentArray with the current client data as well
|
||||
var torrentlistArray = new(Engine.TorrentList)
|
||||
@@ -127,15 +187,21 @@ func main() {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(torrentlistArrayJSON)
|
||||
})
|
||||
http.HandleFunc("/websocket", func(w http.ResponseWriter, r *http.Request) { //websocket is the main data pipe to the frontend
|
||||
|
||||
router.HandleFunc("/websocket", func(w http.ResponseWriter, r *http.Request) { //websocket is the main data pipe to the frontend
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
defer conn.Close() //defer closing the websocket until done.
|
||||
if err != nil {
|
||||
Logger.WithFields(logrus.Fields{"error": err}).Fatal("Unable to create websocket!")
|
||||
return
|
||||
}
|
||||
Engine.Conn = conn //Injecting the conn variable into the other packages
|
||||
if Authenticated != true {
|
||||
handleAuthentication(conn, db)
|
||||
} else { //If we are authenticated inject the connection into the other packages
|
||||
fmt.Println("Authenticated... continue")
|
||||
Engine.Conn = conn
|
||||
Storage.Conn = conn
|
||||
}
|
||||
MessageLoop: //Tagging this so we can continue out of it with any errors we encounter that are failing
|
||||
for {
|
||||
runningTorrents := tclient.Torrents() //getting running torrents here since multiple cases ask for the running torrents
|
||||
@@ -148,6 +214,13 @@ func main() {
|
||||
}
|
||||
Logger.WithFields(logrus.Fields{"message": msg}).Debug("Message From Client")
|
||||
switch msg.MessageType { //first handling data requests
|
||||
case "authRequest":
|
||||
if Authenticated {
|
||||
Logger.WithFields(logrus.Fields{"message": msg}).Debug("Client already authenticated... skipping authentication method")
|
||||
} else {
|
||||
handleAuthentication(conn, db)
|
||||
}
|
||||
|
||||
case "torrentListRequest":
|
||||
Logger.WithFields(logrus.Fields{"message": msg}).Debug("Client Requested TorrentList Update")
|
||||
TorrentLocalArray = Storage.FetchAllStoredTorrents(db) //Required to re-read th database since we write to the DB and this will pull the changes from it
|
||||
@@ -491,9 +564,15 @@ func main() {
|
||||
}
|
||||
|
||||
})
|
||||
if err := http.ListenAndServe(httpAddr, nil); err != nil {
|
||||
if Config.UseProxy {
|
||||
err := http.ListenAndServe(httpAddr, handlers.ProxyHeaders(router))
|
||||
if err != nil {
|
||||
Logger.WithFields(logrus.Fields{"error": err}).Fatal("Unable to listen on the http Server!")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Server started on:", httpAddr)
|
||||
err := http.ListenAndServe(httpAddr, nil) //Can't send proxy headers if not used since that can be a security issue
|
||||
if err != nil {
|
||||
Logger.WithFields(logrus.Fields{"error": err}).Fatal("Unable to listen on the http Server with no proxy headers!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
26
public/static/js/kickwebsocket-manual.js
Normal file
26
public/static/js/kickwebsocket-manual.js
Normal file
@@ -0,0 +1,26 @@
|
||||
//This is the basic template used to generate the kickwebsocket. If needed you can manually edit this one to your needs and replace kickwebsocket-generated.
|
||||
|
||||
IP = "192.168.1.100"
|
||||
Port = "8000"
|
||||
ClientAuthString = "" //String generated on first start and stored in the root as "clientAuth.txt"
|
||||
|
||||
//var ws = new WebSocket(`ws://${IP}:${Port}/websocket`); //for websockets not over an SSL reverse proxy
|
||||
|
||||
//var ws = new WebSocket("wss://yoursubdomain.domain.org/subroute/") //for websockets behind an SSL reverse proxy
|
||||
|
||||
var authMessage = {
|
||||
MessageType: "authRequest",
|
||||
Payload: [ClientAuthString]
|
||||
}
|
||||
|
||||
var kickStart = {
|
||||
MessageType: "torrentListRequest"
|
||||
}
|
||||
|
||||
ws.onopen = function()
|
||||
{
|
||||
ws.send(JSON.stringify(authMessage));
|
||||
console.log("Sending authentication message...", JSON.stringify(authMessage))
|
||||
ws.send(JSON.stringify(kickStart)); //sending out the first ping
|
||||
console.log("Kicking off websocket to server.....", JSON.stringify(kickStart))
|
||||
};
|
@@ -1,12 +0,0 @@
|
||||
var ws = new WebSocket("ws://192.168.1.141:8000/websocket"); //creating websocket
|
||||
|
||||
var kickStart = {
|
||||
MessageType: "torrentListRequest"
|
||||
}
|
||||
|
||||
ws.onopen = function()
|
||||
{
|
||||
|
||||
ws.send(JSON.stringify(kickStart)); //sending out the first ping
|
||||
console.log("Kicking off websocket to server.....", JSON.stringify(kickStart))
|
||||
};
|
BIN
storage.db.old
Normal file
BIN
storage.db.old
Normal file
Binary file not shown.
@@ -15,6 +15,24 @@ var Logger *logrus.Logger
|
||||
//Conn is the global websocket connection used to push server notification messages
|
||||
var Conn *websocket.Conn
|
||||
|
||||
//IssuedTokensList contains a slice of all the tokens issues to applications
|
||||
type IssuedTokensList struct {
|
||||
ID int `storm:"id,unique"` //storm requires unique ID (will be 3) to save although there will only be one of these
|
||||
SigningKey []byte
|
||||
TokenNames []SingleToken
|
||||
}
|
||||
|
||||
//SingleToken stores a single token and all of the associated information
|
||||
type SingleToken struct {
|
||||
ClientName string
|
||||
}
|
||||
|
||||
//TorrentHistoryList holds the entire history of downloaded torrents by hash TODO implement a way to read this and maybe grab the name for every torrent as well
|
||||
type TorrentHistoryList struct {
|
||||
ID int `storm:"id,unique"` //storm requires unique ID (will be 2) to save although there will only be one of these
|
||||
HashList []string
|
||||
}
|
||||
|
||||
//RSSFeedStore stores all of our RSS feeds in a slice of gofeed.Feed
|
||||
type RSSFeedStore struct {
|
||||
ID int `storm:"id,unique"` //storm requires unique ID (will be 1) to save although there will only be one of these
|
||||
@@ -64,12 +82,6 @@ type TorrentLocal struct {
|
||||
TorrentFilePriority []TorrentFilePriority
|
||||
}
|
||||
|
||||
//TorrentHistoryList holds the entire history of downloaded torrents by hash TODO implement a way to read this and maybe grab the name for every torrent as well
|
||||
type TorrentHistoryList struct {
|
||||
ID int `storm:"id,unique"` //storm requires unique ID (will be 2) to save although there will only be one of these
|
||||
HashList []string
|
||||
}
|
||||
|
||||
//FetchAllStoredTorrents is called to read in ALL local stored torrents in the boltdb database (called on server restart)
|
||||
func FetchAllStoredTorrents(torrentStorage *storm.DB) (torrentLocalArray []*TorrentLocal) {
|
||||
torrentLocalArray = []*TorrentLocal{} //creating the array of the torrentlocal struct
|
||||
@@ -188,6 +200,25 @@ func StoreHashHistory(db *storm.DB, torrentHash string) {
|
||||
}
|
||||
}
|
||||
|
||||
//FetchJWTTokens fetches the stored client authentication tokens
|
||||
func FetchJWTTokens(db *storm.DB) IssuedTokensList {
|
||||
tokens := IssuedTokensList{}
|
||||
err := db.One("ID", 3, &tokens)
|
||||
if err != nil {
|
||||
Logger.WithFields(logrus.Fields{"Tokens": tokens, "error": err}).Error("Unable to fetch Token database... should always be one token in database")
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
//UpdateJWTTokens updates the database with new tokens as they are added
|
||||
func UpdateJWTTokens(db *storm.DB, tokens IssuedTokensList) {
|
||||
err := db.Update(&tokens)
|
||||
if err != nil {
|
||||
Logger.WithFields(logrus.Fields{"Tokens": tokens, "error": err}).Error("Unable to update Token database")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//FetchRSSFeeds fetches the RSS feed from db, which was setup when initializing database on first startup
|
||||
func FetchRSSFeeds(db *storm.DB) RSSFeedStore {
|
||||
RSSFeed := RSSFeedStore{}
|
||||
|
@@ -3,13 +3,13 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>goTorrent</title>
|
||||
<link rel="icon" href="/static/favicon/goTorrentFavicon.ico">
|
||||
<script type="text/javascript" src="/static/js/kickwebsocket.js"></script>
|
||||
<link rel="icon" href="static/favicon/goTorrentFavicon.ico">
|
||||
<script type="text/javascript" src="static/js/kickwebsocket-generated.js"></script>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||
</head>
|
||||
<body>
|
||||
<div id=app></div>
|
||||
<script type="text/javascript" src="/static/js/bundle.js"></script>
|
||||
<script type="text/javascript" src="static/js/bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
Reference in New Issue
Block a user