Reverse Proxy with SSL support, Generated client Configs, JWT client to server auth, closes #13

This commit is contained in:
2018-02-07 21:41:00 -05:00
parent 0abe1620c6
commit d6288f4aaa
17 changed files with 1816 additions and 1222 deletions

2
.gitignore vendored
View File

@@ -18,4 +18,6 @@ logs/server.log
.goreleaser.yml .goreleaser.yml
config.toml.backup config.toml.backup
/public/static/js/kickwebsocket.js.backup /public/static/js/kickwebsocket.js.backup
/public/static/js/kickwebsocket-generated.js
clientAuth.txt
dist dist

View File

@@ -39,6 +39,12 @@ Image of the frontend UI
- [X] Add torrents from watch folder (cron job every 5 minutes) - [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 - [ ] 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 - [ ] 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) - [ ] Ability to view TOML settings from WebUI (and perhaps change a few as well)
- [ ] Authentication from client to server
- Late 2018 - Late 2018

View File

@@ -1,12 +1,12 @@
[serverConfig] [serverConfig]
ServerPort = ":8000" #leave format as is it expects a string with colon 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 ServerAddr = "" #blank will bind to default IP address, usually fine to leave be
LogLevel = "Warn" # Options = Debug, Info, Warn, Error, Fatal, Panic LogLevel = "Debug" # Options = Debug, Info, Warn, Error, Fatal, Panic
LogOutput = "file" #Options = file, stdout #file will print it to logs/server.log 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 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. #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 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 TorrentWatchFolder = 'torrentUpload' #folder path that is watched for .torrent files and adds them automatically every 5 minutes
@@ -17,18 +17,30 @@
DownloadRateLimit = "Unlimited" DownloadRateLimit = "Unlimited"
[notifications] [notifications]
PushBulletToken = "" #add your pushbullet api token here to notify of torrent completion to pushbullet 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] [EncryptionPolicy]
DisableEncryption = false DisableEncryption = false
ForceEncryption = false ForceEncryption = false
PreferNoEncryption = true PreferNoEncryption = true
[torrentClientConfig] [torrentClientConfig]
DownloadDir = 'downloading' #the full OR relative path where the torrent server stores in-progress torrents DownloadDir = 'downloading' #the full OR relative path where the torrent server stores in-progress torrents
@@ -37,6 +49,9 @@
# Never send chunks to peers. # Never send chunks to peers.
NoUpload = false #boolean 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. #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" ListenAddr = "" #Leave Blank for default, syntax "HOST:PORT"
@@ -48,9 +63,6 @@
# Don't create a DHT. # Don't create a DHT.
NoDHT = false #boolean NoDHT = false #boolean
#User-provided Client peer ID. If not present, one is generated automatically.
PeerID = "" #string
#For the bittorrent protocol. #For the bittorrent protocol.
DisableUTP = false #bool DisableUTP = false #bool

View 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";
}

View 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)
}
}
}

View 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)
}

View File

@@ -41,7 +41,7 @@ type RSSFeedsNames struct {
} }
//TorrentList struct contains the torrent list that is sent to the client //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"` MessageType string `json:"MessageType"`
Totaltorrents int `json:"total"` Totaltorrents int `json:"total"`
ClientDBstruct []ClientDB `json:"data"` ClientDBstruct []ClientDB `json:"data"`

View File

@@ -3,6 +3,7 @@ package engine
import ( import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"strings"
"golang.org/x/time/rate" "golang.org/x/time/rate"
@@ -17,6 +18,10 @@ type FullClientSettings struct {
LoggingLevel logrus.Level LoggingLevel logrus.Level
LoggingOutput string LoggingOutput string
HTTPAddr string HTTPAddr string
HTTPAddrIP string
UseProxy bool
WebsocketClientPort string
BaseURL string
Version int Version int
TorrentConfig torrent.Config TorrentConfig torrent.Config
TFileUploadFolder string TFileUploadFolder string
@@ -98,9 +103,17 @@ func FullClientSettingsNew() FullClientSettings {
} }
var httpAddr string var httpAddr string
var baseURL string
var websocketClientPort string
httpAddrIP := viper.GetString("serverConfig.ServerAddr") httpAddrIP := viper.GetString("serverConfig.ServerAddr")
httpAddrPort := viper.GetString("serverConfig.ServerPort") 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") seedRatioStop := viper.GetFloat64("serverConfig.SeedRatioStop")
httpAddr = httpAddrIP + httpAddrPort httpAddr = httpAddrIP + httpAddrPort
pushBulletToken := viper.GetString("notifications.PushBulletToken") pushBulletToken := viper.GetString("notifications.PushBulletToken")
@@ -196,7 +209,11 @@ func FullClientSettingsNew() FullClientSettings {
LoggingOutput: logOutput, LoggingOutput: logOutput,
SeedRatioStop: seedRatioStop, SeedRatioStop: seedRatioStop,
HTTPAddr: httpAddr, HTTPAddr: httpAddr,
HTTPAddrIP: httpAddrIP,
UseProxy: proxySet,
WebsocketClientPort: websocketClientPort,
TorrentConfig: tConfig, TorrentConfig: tConfig,
BaseURL: baseURL,
TFileUploadFolder: "uploadedTorrents", TFileUploadFolder: "uploadedTorrents",
PushBulletToken: pushBulletToken, PushBulletToken: pushBulletToken,
DefaultMoveFolder: defaultMoveFolderAbs, DefaultMoveFolder: defaultMoveFolderAbs,

View File

@@ -18,6 +18,8 @@ import LeftMenu from './leftMenu/leftMenu';
import TorrentList from './torrentlist'; import TorrentList from './torrentlist';
//Notification Element //Notification Element
import Notifications from './notifications'; import Notifications from './notifications';
//Login Box
import Login from './login';
@@ -65,6 +67,7 @@ class BasicLayout extends React.PureComponent {
render() { render() {
return [ return [
<Login />,
<Notifications />, <Notifications />,
<ReactGridLayout layout={this.state.layout} onLayoutChange={this.onLayoutChange} <ReactGridLayout layout={this.state.layout} onLayoutChange={this.onLayoutChange}
{...this.props}> {...this.props}>

113
goTorrentWebUI/src/login.js Normal file
View 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
View File

@@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@@ -18,6 +17,8 @@ import (
"github.com/asdine/storm" "github.com/asdine/storm"
Engine "github.com/deranjer/goTorrent/engine" Engine "github.com/deranjer/goTorrent/engine"
Storage "github.com/deranjer/goTorrent/storage" Storage "github.com/deranjer/goTorrent/storage"
jwt "github.com/dgrijalva/jwt-go"
"github.com/gorilla/handlers"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/mmcdole/gofeed" "github.com/mmcdole/gofeed"
@@ -36,6 +37,7 @@ type SingleRSSFeedMessage struct { //TODO had issues with getting this to work w
var ( var (
//Logger does logging for the entire project //Logger does logging for the entire project
Logger = logrus.New() Logger = logrus.New()
Authenticated = false //to verify if user is authenticated, this is stored here
APP_ID = os.Getenv("APP_ID") 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}) 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() { func main() {
Engine.Logger = Logger //Injecting the logger into all the packages Engine.Logger = Logger //Injecting the logger into all the packages
Storage.Logger = Logger Storage.Logger = Logger
@@ -92,6 +122,34 @@ func main() {
} }
defer db.Close() //defering closing the database until the program closes 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 cronEngine := Engine.InitializeCronEngine() //Starting the cron engine for tasks
Logger.Debug("Cron Engine Initialized...") 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 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 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 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.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) TorrentLocalArray = Storage.FetchAllStoredTorrents(db)
RunningTorrentArray = Engine.CreateRunningTorrentArray(tclient, TorrentLocalArray, PreviousTorrentArray, Config, db) //Updates the RunningTorrentArray with the current client data as well RunningTorrentArray = Engine.CreateRunningTorrentArray(tclient, TorrentLocalArray, PreviousTorrentArray, Config, db) //Updates the RunningTorrentArray with the current client data as well
var torrentlistArray = new(Engine.TorrentList) var torrentlistArray = new(Engine.TorrentList)
@@ -127,15 +187,21 @@ func main() {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.Write(torrentlistArrayJSON) 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) conn, err := upgrader.Upgrade(w, r, nil)
defer conn.Close() //defer closing the websocket until done. defer conn.Close() //defer closing the websocket until done.
if err != nil { if err != nil {
Logger.WithFields(logrus.Fields{"error": err}).Fatal("Unable to create websocket!") Logger.WithFields(logrus.Fields{"error": err}).Fatal("Unable to create websocket!")
return 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 Storage.Conn = conn
}
MessageLoop: //Tagging this so we can continue out of it with any errors we encounter that are failing MessageLoop: //Tagging this so we can continue out of it with any errors we encounter that are failing
for { for {
runningTorrents := tclient.Torrents() //getting running torrents here since multiple cases ask for the running torrents 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") Logger.WithFields(logrus.Fields{"message": msg}).Debug("Message From Client")
switch msg.MessageType { //first handling data requests 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": case "torrentListRequest":
Logger.WithFields(logrus.Fields{"message": msg}).Debug("Client Requested TorrentList Update") 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 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!") Logger.WithFields(logrus.Fields{"error": err}).Fatal("Unable to listen on the http Server!")
}
} else { } 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

View 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))
};

View File

@@ -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

Binary file not shown.

View File

@@ -15,6 +15,24 @@ var Logger *logrus.Logger
//Conn is the global websocket connection used to push server notification messages //Conn is the global websocket connection used to push server notification messages
var Conn *websocket.Conn 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 //RSSFeedStore stores all of our RSS feeds in a slice of gofeed.Feed
type RSSFeedStore struct { 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 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 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) //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) { func FetchAllStoredTorrents(torrentStorage *storm.DB) (torrentLocalArray []*TorrentLocal) {
torrentLocalArray = []*TorrentLocal{} //creating the array of the torrentlocal struct 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 //FetchRSSFeeds fetches the RSS feed from db, which was setup when initializing database on first startup
func FetchRSSFeeds(db *storm.DB) RSSFeedStore { func FetchRSSFeeds(db *storm.DB) RSSFeedStore {
RSSFeed := RSSFeedStore{} RSSFeed := RSSFeedStore{}

View File

@@ -3,13 +3,13 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>goTorrent</title> <title>goTorrent</title>
<link rel="icon" href="/static/favicon/goTorrentFavicon.ico"> <link rel="icon" href="static/favicon/goTorrentFavicon.ico">
<script type="text/javascript" src="/static/js/kickwebsocket.js"></script> <script type="text/javascript" src="static/js/kickwebsocket-generated.js"></script>
<meta name='viewport' content='width=device-width, initial-scale=1.0'> <meta name='viewport' content='width=device-width, initial-scale=1.0'>
</head> </head>
<body> <body>
<div id=app></div> <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> </body>
</html> </html>
{{end}} {{end}}