Add accounts

This commit is contained in:
Eliot Whalan 2017-02-05 14:02:32 +10:00
parent 8c153440cf
commit 073edf820b
No known key found for this signature in database
GPG Key ID: C0A42175139840D6
9 changed files with 594 additions and 40 deletions

View File

@ -22,6 +22,8 @@ install:
go get github.com/gorilla/mux go get github.com/gorilla/mux
go get github.com/go-sql-driver/mysql go get github.com/go-sql-driver/mysql
go get github.com/lib/pq go get github.com/lib/pq
go get golang.org/x/crypto/bcrypt
go get github.com/gorilla/securecookie
test: install test: install
go install $(GOFLAGS) ./... go install $(GOFLAGS) ./...

BIN
Pastebin Executable file

Binary file not shown.

View File

@ -88,9 +88,9 @@
<div class="pull-right"> <div class="pull-right">
<label class="control-label ">&nbsp;</label> <label class="control-label ">&nbsp;</label>
<div class="row"> <div class="row">
<button class="btn btn-raised btn-primary" id="button-save">Submit<div class="ripple-container"></button> <button class="btn btn-raised btn-primary" id="button-save">Submit<div class="ripple-container"></button>
</div> </div>
</div> </div>
</div> </div>
@ -119,20 +119,30 @@
<span class='swal-bold'> Create Paste</span> \ <span class='swal-bold'> Create Paste</span> \
<span class='swal-code'>echo '{&quot;paste&quot;: &quot;Hello FooBar&quot;}' | curl -H 'Content-Type: application/json' -d @- {{ .UrlAddress }}/api </span> \ <span class='swal-code'>echo '{&quot;paste&quot;: &quot;Hello FooBar&quot;}' | curl -H 'Content-Type: application/json' -d @- {{ .UrlAddress }}/api </span> \
\ \
<span class='swal-bold'> Create Paste</span> \
<span class='swal-code'>echo '{&quot;paste&quot;: &quot;Hello FooBar&quot;,&quot;key&quot;: &quot;{{.UserKey}}&quot;}' | curl -H 'Content-Type: application/json' -d @- {{ .UrlAddress }}/api </span> \
\
<span class='swal-bold'> Delete Paste </span> \ <span class='swal-bold'> Delete Paste </span> \
<span class='swal-code'> curl -X DELETE -F 'delkey=insert-your-delete-key-here' {{ .UrlAddress }}/api/{pasteid} </span> \ <span class='swal-code'> curl -X DELETE -F 'delkey=insert-your-delete-key-here' {{ .UrlAddress }}/api/{pasteid} </span> \
\ \
<span class='swal-bold'> Show Paste </span> \ <span class='swal-bold'> Show Paste </span> \
<span class='swal-code'> {{ .UrlAddress }}/p/{passte-id} </span> \ <span class='swal-code'> {{ .UrlAddress }}/p/{paste-id} </span> \
\ \
<span class='swal-bold'> Show Paste with a specific language </span> \ <span class='swal-bold'> Show Paste with a specific language </span> \
<span class='swal-code'> {{ .UrlAddress }}/p/{passte-id}/{language} </span> \ <span class='swal-code'> {{ .UrlAddress }}/p/{paste-id}/{language} </span> \
\ \
<span class='swal-bold'> Show Paste with a specific language and style </span> \ <span class='swal-bold'> Show Paste with a specific language and style </span> \
<span class='swal-code'> {{ .UrlAddress }}/p/{passte-id}/{language}/{style} </span> \ <span class='swal-code'> {{ .UrlAddress }}/p/{paste-id}/{language}/{style} </span> \
<span class='swal-bold'> Notes, </span> \ <span class='swal-bold'> Notes, </span> \
<span class='swal-code'> * Languages and Styles are standard components of the Python Syntax Highlighter (pygments)</span><br>\ <span class='swal-code'> * Languages and Styles are standard components of the Python Syntax Highlighter (pygments)</span><br>\
\ \
<span class='swal-bold'>User accounts </span> \
<span class='swal-code'>If you would like to save your pastes please register for an account at <a href="/register">register</a></span><br>\
<span class='swal-code'>To view and delete your pastes:<a href="/pastes">register</a></span><br>\
\
<span class='swal-bold'> API key, Keep secret </span> \
<span class='swal-code' id='key'>{{.UserKey}}</span><br>\
\
<span class='swal-code'>Source: <a href='https://github.com/ewhal/Pastebin'>Github</a></span>\ <span class='swal-code'>Source: <a href='https://github.com/ewhal/Pastebin'>Github</a></span>\
<span class='swal-code'>Tools: <a href='https://github.com/ewhal/scripts/blob/master/paste.sh'>Paste.sh</a></span>", <span class='swal-code'>Tools: <a href='https://github.com/ewhal/scripts/blob/master/paste.sh'>Paste.sh</a></span>",
html: true html: true
@ -157,11 +167,13 @@
var data_expiry = $("#button-expiry").attr("value"); var data_expiry = $("#button-expiry").attr("value");
var data_title = $("#title").val(); var data_title = $("#title").val();
var data_paste = $("#paste").val(); var data_paste = $("#paste").val();
var user_key = {{.UserKey}};
var json_data = { expiry : data_expiry, var json_data = { expiry : data_expiry,
title : data_title, title : data_title,
paste : data_paste, paste : data_paste,
lang : data_lang, lang : data_lang,
userkey: user_key,
webreq : true }; webreq : true };
$.ajax({ $.ajax({

93
assets/login.html Normal file
View File

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Login</title>
<!-- Material Design fonts -->
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700">
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/bootstrap.material-design/0.5.10/css/bootstrap-material-design.min.css" integrity="sha256-j3CLSRG31GkOu6kaeLh7XsRgL2YNvRl9aOtXoAYt320=" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/bootstrap.material-design/0.5.10/css/ripples.min.css" integrity="sha256-+Og2qJI9qzvKYwhGo/LYXg0FzE1BhEQfDsUSjKXQ3Bg=" crossorigin="anonymous">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="bs-component">
<div class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-responsive-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Home</a>
</div>
<div class="navbar-collapse collapse navbar-responsive-collapse">
<ul class="nav navbar-nav">
<li><a href="/pastes">pastes</a></li>
<li><a href="/register">Register</a></li>
<li><a href="/login">Login</a></li>
<li><a href="/logout">Logout</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="container">
<div class="well bs-component">
<form class="form-horizontal" action="/login" method="POST">
<fieldset>
<legend>Login</legend>
<div class="form-group is-empty">
<label for="inputEmail" class="col-md-2 control-label">Email</label>
<div class="col-md-10">
<input type="email" class="form-control" id="inputEmail" placeholder="Email" required name="email">
</div>
</div>
<div class="form-group is-empty">
<label for="inputPassword" class="col-md-2 control-label">Password</label>
<div class="col-md-10">
<input type="password" class="form-control" id="inputPassword" placeholder="Password" required name="password">
</div>
</div>
<div class="form-group">
<div class="col-md-10 pull-right">
<button type="submit" class="btn btn-raised btn-primary">Submit<div class="ripple-container"></div></button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/bootstrap.material-design/0.5.10/js/material.min.js" integrity="sha256-uZbIqasulk7Y9yEwknbeQ0FpF3aUhtPwuggbpvQaI8Y=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/bootstrap.material-design/0.5.10/js/ripples.min.js" integrity="sha256-TY/EO/++Ug/P+fSBjaqlmtuphCBKwlP7TOnS+SGnN8g=" crossorigin="anonymous"></script>
<script>
$.material.init();
</script>
</body>
</html>

121
assets/pastes.html Normal file
View File

@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Your Pastes</title>
<!-- Material Design fonts -->
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700">
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/bootstrap.material-design/0.5.10/css/bootstrap-material-design.min.css" integrity="sha256-j3CLSRG31GkOu6kaeLh7XsRgL2YNvRl9aOtXoAYt320=" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/bootstrap.material-design/0.5.10/css/ripples.min.css" integrity="sha256-+Og2qJI9qzvKYwhGo/LYXg0FzE1BhEQfDsUSjKXQ3Bg=" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontawesome/4.6.3/css/font-awesome.min.css" integrity="sha256-AIodEDkC8V/bHBkfyxzolUMw57jeQ9CauwhVW6YJ9CA=" crossorigin="anonymous">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="bs-component">
<div class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-responsive-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Home</a>
</div>
<div class="navbar-collapse collapse navbar-responsive-collapse">
<ul class="nav navbar-nav">
<li><a href="/pastes">User</a></li>
<li><a href="/register">Register</a></li>
<li><a href="/login">Login</a></li>
<li><a href="/logout">Logout</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="container">
<table class="table table-hover" id="local">
<thead>
<th>URL</th>
<th>Title</th>
<th>Size</th>
<th>Delete</th>
</thead>
<tbody>
{{ range .Response}}
<tr>
<td><a href="{{.Url}}">{{.Id}}</a></td>
<td>{{.Title}}</td>
<td>{{.Size}}</td>
<td><button class="del" id="{{.Id}}" value="{{.DelKey}}">{{.Id}}</button></td>
</tr>
{{end}}
</tbody>
</table>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/bootstrap.material-design/0.5.10/js/material.min.js" integrity="sha256-uZbIqasulk7Y9yEwknbeQ0FpF3aUhtPwuggbpvQaI8Y=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/bootstrap.material-design/0.5.10/js/ripples.min.js" integrity="sha256-TY/EO/++Ug/P+fSBjaqlmtuphCBKwlP7TOnS+SGnN8g=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Dynatable/0.3.1/jquery.dynatable.min.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Dynatable/0.3.1/jquery.dynatable.min.js"></script>
<script>
$.material.init();
$(document).ready(function() {
$('#local').dynatable();
$( '.del' ).click(function() {
var Id = $(this).text();
var del_key = $(this).val();
$(this).closest('tr').remove();
var json_data = {delkey: del_key,
webreq : true };
$.ajax({
url: "http://localhost:9999/api/" + Id,
type: 'DELETE',
contentType: "application/json; charset=utf-8",
data: JSON.stringify(json_data),
dataType: "json",
success: function(json){
// window.location = json.url+"/"+data_lang;
},
error: function(json){
// sweetAlert("", json.responseText, "error");
}
});
});
} );
</script>
</body>
</html>

93
assets/register.html Normal file
View File

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Register</title>
<!-- Material Design fonts -->
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700">
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/bootstrap.material-design/0.5.10/css/bootstrap-material-design.min.css" integrity="sha256-j3CLSRG31GkOu6kaeLh7XsRgL2YNvRl9aOtXoAYt320=" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/bootstrap.material-design/0.5.10/css/ripples.min.css" integrity="sha256-+Og2qJI9qzvKYwhGo/LYXg0FzE1BhEQfDsUSjKXQ3Bg=" crossorigin="anonymous">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="bs-component">
<div class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-responsive-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Home</a>
</div>
<div class="navbar-collapse collapse navbar-responsive-collapse">
<ul class="nav navbar-nav">
<li><a href="/pastes">User</a></li>
<li><a href="/register">Register</a></li>
<li><a href="/login">Login</a></li>
<li><a href="/logout">Logout</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="container">
<div class="well bs-component">
<form class="form-horizontal" action="/register" method="POST">
<fieldset>
<legend>Register</legend>
<div class="form-group is-empty">
<label for="inputEmail" class="col-md-2 control-label">Email</label>
<div class="col-md-10">
<input type="email" class="form-control" id="inputEmail" placeholder="Email" required name="email">
</div>
</div>
<div class="form-group is-empty">
<label for="inputPassword" class="col-md-2 control-label">Password</label>
<div class="col-md-10">
<input type="password" class="form-control" id="inputPassword" placeholder="Password" required name="password">
</div>
</div>
<div class="form-group">
<div class="col-md-10 pull-right">
<button type="submit" class="btn btn-raised btn-primary">Submit<div class="ripple-container"></div></button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/bootstrap.material-design/0.5.10/js/material.min.js" integrity="sha256-uZbIqasulk7Y9yEwknbeQ0FpF3aUhtPwuggbpvQaI8Y=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/bootstrap.material-design/0.5.10/js/ripples.min.js" integrity="sha256-TY/EO/++Ug/P+fSBjaqlmtuphCBKwlP7TOnS+SGnN8g=" crossorigin="anonymous"></script>
<script>
$.material.init();
</script>
</body>
</html>

View File

@ -3,6 +3,7 @@
"dbhost": "", "dbhost": "",
"dbname": "pastebin.db", "dbname": "pastebin.db",
"dbtable": "pastebin", "dbtable": "pastebin",
"dbaccountstable": "accounts",
"dbtype": "sqlite3", "dbtype": "sqlite3",
"dbport": "", "dbport": "",
"dbuser":"", "dbuser":"",

View File

@ -5,13 +5,13 @@ CREATE TABLE `pastebin` (
`data` longtext, `data` longtext,
`delkey` char(40) default NULL, `delkey` char(40) default NULL,
`expiry` int, `expiry` int,
`userid` int, `userid` varchar(255),
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
); );
CREATE TABLE `accounts` ( CREATE TABLE `accounts` (
`id` varchar(30) NOT NULL,
`email` varchar(255) NOT NULL, `email` varchar(255) NOT NULL,
`pass` varchar(255) NOT NULL, `password` varchar(255) NOT NULL,
PRIMARY KEY (`id`) `key` varchar(255) NOT NULL,
PRIMARY KEY (`key`)
); );

View File

@ -30,25 +30,30 @@ import (
// For url routing // For url routing
"github.com/gorilla/mux" "github.com/gorilla/mux"
// securecookie for cookie handling
"github.com/gorilla/securecookie"
// bcrypt for password hashing
"golang.org/x/crypto/bcrypt"
) )
// Configuration struct, // Configuration struct,
type Configuration struct { type Configuration struct {
Address string `json:"address"` // Url to to the pastebin Address string `json:"address"` // Url to to the pastebin
DBHost string `json:"dbhost"` // Name of your database host DBHost string `json:"dbhost"` // Name of your database host
DBName string `json:"dbname"` // Name of your database DBName string `json:"dbname"` // Name of your database
DBPassword string `json:"dbpassword"` // The password for the database user DBPassword string `json:"dbpassword"` // The password for the database user
DBPlaceHolder [6]string // ? / $[i] Depending on db driver. DBPlaceHolder [7]string // ? / $[i] Depending on db driver.
DBPort string `json:"dbport"` // Port of the database DBPort string `json:"dbport"` // Port of the database
DBTable string `json:"dbtable"` // Name of the table in the database DBTable string `json:"dbtable"` // Name of the table in the database
DBType string `json:"dbtype"` // Type of database DBAccountsTable string `json:"dbaccountstable"` // Name of the table in the database
DBUser string `json:"dbuser"` // The database user DBType string `json:"dbtype"` // Type of database
DisplayName string `json:"displayname"` // Name of your pastebin DBUser string `json:"dbuser"` // The database user
GoogleAPIKey string `json:"googleapikey"` // Your google api key DisplayName string `json:"displayname"` // Name of your pastebin
Highlighter string `json:"highlighter"` // The name of the highlighter. GoogleAPIKey string `json:"googleapikey"` // Your google api key
ListenAddress string `json:"listenaddress"` // Address that pastebin will bind on Highlighter string `json:"highlighter"` // The name of the highlighter.
ListenPort string `json:"listenport"` // Port that pastebin will listen on ListenAddress string `json:"listenaddress"` // Address that pastebin will bind on
ShortUrlLength int `json:"shorturllength,string"` // Length of the generated short urls ListenPort string `json:"listenport"` // Port that pastebin will listen on
ShortUrlLength int `json:"shorturllength,string"` // Length of the generated short urls
} }
// This struct is used for responses. // This struct is used for responses.
@ -70,14 +75,15 @@ type Response struct {
// This struct is used for indata when a request is being made to the pastebin. // This struct is used for indata when a request is being made to the pastebin.
type Request struct { type Request struct {
DelKey string `json:"delkey"` // The delkey that is used to delete paste DelKey string `json:"delkey"` // The delkey that is used to delete paste
Expiry int64 `json:"expiry,string"` // An expiry date Expiry int64 `json:"expiry,string"` // An expiry date
Id string `json:"id"` // The id of the paste Id string `json:"id"` // The id of the paste
Lang string `json:"lang"` // The language of the paste Lang string `json:"lang"` // The language of the paste
Paste string `json:"paste"` // The actual pase Paste string `json:"paste"` // The actual pase
Style string `json:"style"` // The style of the paste Style string `json:"style"` // The style of the paste
Title string `json:"title"` // The title of the paste Title string `json:"title"` // The title of the paste
WebReq bool `json:"webreq"` // If its a webrequest or not UserKey string `json:"key"` // The title of the paste
WebReq bool `json:"webreq"` // If its a webrequest or not
} }
// This struct is used for generating pages. // This struct is used for generating pages.
@ -98,11 +104,18 @@ type Page struct {
UrlHome string UrlHome string
UrlRaw string UrlRaw string
WrapperErr string WrapperErr string
UserKey string
}
type Pastes struct {
Response []Response
} }
// Template pages, // Template pages,
var templates = template.Must(template.ParseFiles("assets/index.html", var templates = template.Must(template.ParseFiles("assets/index.html",
"assets/syntax.html")) "assets/syntax.html",
"assets/register.html",
"assets/pastes.html",
"assets/login.html"))
// Global variables, *shrug* // Global variables, *shrug*
var configuration Configuration var configuration Configuration
@ -113,6 +126,12 @@ var listOfLangsFirst map[string]string
var listOfLangsLast map[string]string var listOfLangsLast map[string]string
var listOfStyles map[string]string var listOfStyles map[string]string
// generate new random cookie keys
var cookieHandler = securecookie.New(
securecookie.GenerateRandomKey(64),
securecookie.GenerateRandomKey(32),
)
// //
// Functions below, // Functions below,
// //
@ -261,7 +280,7 @@ func checkArgs() {
func getDBHandle() *sql.DB { func getDBHandle() *sql.DB {
var dbinfo string var dbinfo string
for i := 0; i < 6; i++ { for i := 0; i < 7; i++ {
configuration.DBPlaceHolder[i] = "?" configuration.DBPlaceHolder[i] = "?"
} }
@ -280,7 +299,7 @@ func getDBHandle() *sql.DB {
configuration.DBUser, configuration.DBUser,
configuration.DBPassword, configuration.DBPassword,
configuration.DBName) configuration.DBName)
for i := 0; i < 6; i++ { for i := 0; i < 7; i++ {
configuration.DBPlaceHolder[i] = "$" + strconv.Itoa(i+1) configuration.DBPlaceHolder[i] = "$" + strconv.Itoa(i+1)
} }
@ -365,13 +384,14 @@ func shaPaste(paste string) string {
// paste, the actual paste data as a string, // paste, the actual paste data as a string,
// expiry, the epxpiry date in epoch time as an int64 // expiry, the epxpiry date in epoch time as an int64
// Returns the Response struct // Returns the Response struct
func savePaste(title string, paste string, expiry int64) Response { func savePaste(title string, paste string, expiry int64, user_key string) Response {
var id, hash, delkey, url string var id, hash, delkey, url string
// Escape user input, // Escape user input,
paste = html.EscapeString(paste) paste = html.EscapeString(paste)
title = html.EscapeString(title) title = html.EscapeString(title)
user_key = html.EscapeString(user_key)
// Hash paste data and query database to see if paste exists // Hash paste data and query database to see if paste exists
sha := shaPaste(paste) sha := shaPaste(paste)
@ -419,15 +439,15 @@ func savePaste(title string, paste string, expiry int64) Response {
// This is needed since mysql/postgres uses different placeholders, // This is needed since mysql/postgres uses different placeholders,
var dbQuery string var dbQuery string
for i := 0; i < 6; i++ { for i := 0; i < 7; i++ {
dbQuery += configuration.DBPlaceHolder[i] + "," dbQuery += configuration.DBPlaceHolder[i] + ","
} }
dbQuery = dbQuery[:len(dbQuery)-1] dbQuery = dbQuery[:len(dbQuery)-1]
stmt, err := dbHandle.Prepare("INSERT INTO " + configuration.DBTable + " (id,title,hash,data,delkey,expiry)values(" + dbQuery + ")") stmt, err := dbHandle.Prepare("INSERT INTO " + configuration.DBTable + " (id,title,hash,data,delkey,expiry,userid)values(" + dbQuery + ")")
checkErr(err) checkErr(err)
_, err = stmt.Exec(id, title, sha, paste, delKey, expiry) _, err = stmt.Exec(id, title, sha, paste, delKey, expiry, user_key)
checkErr(err) checkErr(err)
loggy(fmt.Sprintf("Sucessfully inserted data at id '%s', title '%s', expiry '%v' and data \n \n* * * *\n\n%s\n\n* * * *\n", loggy(fmt.Sprintf("Sucessfully inserted data at id '%s', title '%s', expiry '%v' and data \n \n* * * *\n\n%s\n\n* * * *\n",
@ -453,6 +473,9 @@ func savePaste(title string, paste string, expiry int64) Response {
func DelHandler(w http.ResponseWriter, r *http.Request) { func DelHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
var inData Request var inData Request
loggy(fmt.Sprintf("Recieving request to delete a paste, trying to parse indata."))
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&inData)
inData.Id = vars["pasteId"] inData.Id = vars["pasteId"]
@ -516,7 +539,7 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
p := savePaste(inData.Title, inData.Paste, inData.Expiry) p := savePaste(inData.Title, inData.Paste, inData.Expiry, inData.UserKey)
d, _ = json.MarshalIndent(p, "DEBUG : ", " ") d, _ = json.MarshalIndent(p, "DEBUG : ", " ")
loggy(fmt.Sprintf("Returning json data to requester \nDEBUG : %s", d)) loggy(fmt.Sprintf("Returning json data to requester \nDEBUG : %s", d))
@ -802,6 +825,7 @@ func CloneHandler(w http.ResponseWriter, r *http.Request) {
Body: template.HTML(p.Paste), Body: template.HTML(p.Paste),
PasteTitle: "Copy of " + p.Title, PasteTitle: "Copy of " + p.Title,
Title: "Copy of " + p.Title, Title: "Copy of " + p.Title,
UserKey: getUserKey(r),
} }
err := templates.ExecuteTemplate(w, "index.html", page) err := templates.ExecuteTemplate(w, "index.html", page)
@ -835,6 +859,209 @@ func RawHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, p.Paste) io.WriteString(w, p.Paste)
} }
// loginHandler
func loginHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
err := templates.ExecuteTemplate(w, "login.html", "")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
case "POST":
email := r.FormValue("email")
password := r.FormValue("password")
email_escaped := html.EscapeString(email)
// Query database if id exists and if it does call generateName again
var hashedPassword []byte
err := dbHandle.QueryRow("select password from "+configuration.DBAccountsTable+
" where email="+configuration.DBPlaceHolder[0], email_escaped).
Scan(&hashedPassword)
switch {
case err == sql.ErrNoRows:
loggy(fmt.Sprintf("Email '%s' is not taken.", email))
http.Redirect(w, r, "/register", 302)
case err != nil:
debugLogger.Println(" Database error : " + err.Error())
os.Exit(1)
default:
loggy(fmt.Sprintf("Account '%s' exists.", email))
}
// compare bcrypt hash to userinput password
err = bcrypt.CompareHashAndPassword(hashedPassword, []byte(password))
if err == nil {
// prepare cookie
value := map[string]string{
"email": email,
}
// encode variables into cookie
if encoded, err := cookieHandler.Encode("session", value); err == nil {
cookie := &http.Cookie{
Name: "session",
Value: encoded,
Path: "/",
}
// set user cookie
http.SetCookie(w, cookie)
}
loggy(fmt.Sprintf("Successfully logged account '%s' in.", email))
// Redirect to home page
http.Redirect(w, r, "/", 302)
}
// Redirect to login page
http.Redirect(w, r, "/login", 302)
}
}
func pastesHandler(w http.ResponseWriter, r *http.Request) {
key := getUserKey(r)
b := Pastes{Response: []Response{}}
rows, err := dbHandle.Query("select id, title, delkey, data from "+
configuration.DBTable+" where userid="+
configuration.DBPlaceHolder[0], key)
switch {
case err == sql.ErrNoRows:
loggy("Pasted data is not in the database, will insert it.")
case err != nil:
debugLogger.Println(" Database error : " + err.Error())
os.Exit(1)
default:
for rows.Next() {
var id, title, url, delKey, data string
rows.Scan(&id, &title, &delKey, &data)
url = configuration.Address + "/p/" + id
res := Response{
Id: id,
Title: title,
Url: url,
Size: len(data),
DelKey: delKey}
b.Response = append(b.Response, res)
}
rows.Close()
}
err = templates.ExecuteTemplate(w, "pastes.html", &b)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
// loggedIn returns true if cookie exists
func getUserKey(r *http.Request) string {
cookie, err := r.Cookie("session")
cookieValue := make(map[string]string)
if err != nil {
return ""
}
err = cookieHandler.Decode("session", cookie.Value, &cookieValue)
if err != nil {
return ""
}
email := cookieValue["email"]
// Query database if id exists and if it does call generateName again
var user_key string
err = dbHandle.QueryRow("select key from "+configuration.DBAccountsTable+
" where email="+configuration.DBPlaceHolder[0], email).
Scan(&user_key)
switch {
case err == sql.ErrNoRows:
loggy(fmt.Sprintf("Key does not exist for user '%s'", email))
case err != nil:
debugLogger.Println(" Database error : " + err.Error())
os.Exit(1)
default:
loggy(fmt.Sprintf("User key found for user '%s'", email))
}
return user_key
}
// registerHandler
func registerHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
err := templates.ExecuteTemplate(w, "register.html", "")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
case "POST":
email := r.FormValue("email")
pass := r.FormValue("password")
email_escaped := html.EscapeString(email)
loggy(fmt.Sprintf("Attempting to create account '%s', checking if it's already taken in the database",
email))
// Query database if id exists and if it does call generateName again
var email_taken string
err := dbHandle.QueryRow("select email from "+configuration.DBAccountsTable+
" where email="+configuration.DBPlaceHolder[0], email_escaped).
Scan(&email_taken)
switch {
case err == sql.ErrNoRows:
loggy(fmt.Sprintf("Email '%s' is not taken, will use it.", email))
case err != nil:
debugLogger.Println(" Database error : " + err.Error())
os.Exit(1)
default:
loggy(fmt.Sprintf("Email '%s' is taken.", email_taken))
http.Redirect(w, r, "/register", 302)
}
// This is needed since mysql/postgres uses different placeholders,
var dbQuery string
for i := 0; i < 3; i++ {
dbQuery += configuration.DBPlaceHolder[i] + ","
}
dbQuery = dbQuery[:len(dbQuery)-1]
stmt, err := dbHandle.Prepare("INSERT into " + configuration.DBAccountsTable + "(email, password, key) values(" + dbQuery + ")")
checkErr(err)
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
checkErr(err)
key := uniuri.NewLen(24)
_, err = stmt.Exec(email_escaped, hashedPassword, key)
checkErr(err)
loggy(fmt.Sprintf("Successfully created account '%s' with hashed password '%s'",
email,
hashedPassword))
stmt.Close()
checkErr(err)
http.Redirect(w, r, "/login", 302)
}
}
// logoutHandler destroys cookie data and redirects to root
func logoutHandler(w http.ResponseWriter, r *http.Request) {
cookie := &http.Cookie{
Name: "session",
Value: "",
Path: "/",
MaxAge: -1,
}
http.SetCookie(w, cookie)
http.Redirect(w, r, "/", 301)
}
// RootHandler handles generating the root page // RootHandler handles generating the root page
func RootHandler(w http.ResponseWriter, r *http.Request) { func RootHandler(w http.ResponseWriter, r *http.Request) {
@ -843,6 +1070,7 @@ func RootHandler(w http.ResponseWriter, r *http.Request) {
LangsLast: listOfLangsLast, LangsLast: listOfLangsLast,
Title: configuration.DisplayName, Title: configuration.DisplayName,
UrlAddress: configuration.Address, UrlAddress: configuration.Address,
UserKey: getUserKey(r),
} }
err := templates.ExecuteTemplate(w, "index.html", p) err := templates.ExecuteTemplate(w, "index.html", p)
@ -906,6 +1134,10 @@ func main() {
router.HandleFunc("/raw/{pasteId}", RawHandler).Methods("GET") router.HandleFunc("/raw/{pasteId}", RawHandler).Methods("GET")
router.HandleFunc("/clone/{pasteId}", CloneHandler).Methods("GET") router.HandleFunc("/clone/{pasteId}", CloneHandler).Methods("GET")
router.HandleFunc("/login", loginHandler)
router.HandleFunc("/logout", logoutHandler)
router.HandleFunc("/register", registerHandler)
router.HandleFunc("/pastes", pastesHandler).Methods("GET")
router.HandleFunc("/download/{pasteId}", DownloadHandler).Methods("GET") router.HandleFunc("/download/{pasteId}", DownloadHandler).Methods("GET")
router.HandleFunc("/assets/pastebin.css", serveCss).Methods("GET") router.HandleFunc("/assets/pastebin.css", serveCss).Methods("GET")