diff --git a/Makefile b/Makefile
index 7825d2c..2b3e7e3 100644
--- a/Makefile
+++ b/Makefile
@@ -22,6 +22,8 @@ install:
go get github.com/gorilla/mux
go get github.com/go-sql-driver/mysql
go get github.com/lib/pq
+ go get golang.org/x/crypto/bcrypt
+ go get github.com/gorilla/securecookie
test: install
go install $(GOFLAGS) ./...
diff --git a/Pastebin b/Pastebin
new file mode 100755
index 0000000..8e58781
Binary files /dev/null and b/Pastebin differ
diff --git a/assets/index.html b/assets/index.html
index 3d8e81e..ebfec52 100644
--- a/assets/index.html
+++ b/assets/index.html
@@ -88,9 +88,9 @@
@@ -119,20 +119,30 @@
Create Paste \
echo '{"paste": "Hello FooBar"}' | curl -H 'Content-Type: application/json' -d @- {{ .UrlAddress }}/api \
\
+ Create Paste \
+ echo '{"paste": "Hello FooBar","key": "{{.UserKey}}"}' | curl -H 'Content-Type: application/json' -d @- {{ .UrlAddress }}/api \
+ \
Delete Paste \
curl -X DELETE -F 'delkey=insert-your-delete-key-here' {{ .UrlAddress }}/api/{pasteid} \
\
Show Paste \
- {{ .UrlAddress }}/p/{passte-id} \
+ {{ .UrlAddress }}/p/{paste-id} \
\
Show Paste with a specific language \
- {{ .UrlAddress }}/p/{passte-id}/{language} \
+ {{ .UrlAddress }}/p/{paste-id}/{language} \
\
Show Paste with a specific language and style \
- {{ .UrlAddress }}/p/{passte-id}/{language}/{style} \
+ {{ .UrlAddress }}/p/{paste-id}/{language}/{style} \
Notes, \
* Languages and Styles are standard components of the Python Syntax Highlighter (pygments) \
\
+ User accounts \
+ If you would like to save your pastes please register for an account at register \
+ To view and delete your pastes:register \
+ \
+ API key, Keep secret \
+ {{.UserKey}} \
+ \
Source: Github \
Tools: Paste.sh ",
html: true
@@ -157,11 +167,13 @@
var data_expiry = $("#button-expiry").attr("value");
var data_title = $("#title").val();
var data_paste = $("#paste").val();
+ var user_key = {{.UserKey}};
var json_data = { expiry : data_expiry,
title : data_title,
paste : data_paste,
lang : data_lang,
+ userkey: user_key,
webreq : true };
$.ajax({
diff --git a/assets/login.html b/assets/login.html
new file mode 100644
index 0000000..9d53116
--- /dev/null
+++ b/assets/login.html
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+ Login
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/pastes.html b/assets/pastes.html
new file mode 100644
index 0000000..ef5db80
--- /dev/null
+++ b/assets/pastes.html
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+ Your Pastes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ URL
+ Title
+ Size
+ Delete
+
+
+ {{ range .Response}}
+
+ {{.Id}}
+ {{.Title}}
+ {{.Size}}
+ {{.Id}}
+
+ {{end}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/register.html b/assets/register.html
new file mode 100644
index 0000000..2f16479
--- /dev/null
+++ b/assets/register.html
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+ Register
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config.json b/config.json
index 309eabd..c3f8cec 100644
--- a/config.json
+++ b/config.json
@@ -3,6 +3,7 @@
"dbhost": "",
"dbname": "pastebin.db",
"dbtable": "pastebin",
+ "dbaccountstable": "accounts",
"dbtype": "sqlite3",
"dbport": "",
"dbuser":"",
diff --git a/database.sql b/database.sql
index da9f4cf..2a6adb2 100644
--- a/database.sql
+++ b/database.sql
@@ -5,13 +5,13 @@ CREATE TABLE `pastebin` (
`data` longtext,
`delkey` char(40) default NULL,
`expiry` int,
- `userid` int,
+ `userid` varchar(255),
PRIMARY KEY (`id`)
);
CREATE TABLE `accounts` (
- `id` varchar(30) NOT NULL,
`email` varchar(255) NOT NULL,
- `pass` varchar(255) NOT NULL,
- PRIMARY KEY (`id`)
+ `password` varchar(255) NOT NULL,
+ `key` varchar(255) NOT NULL,
+ PRIMARY KEY (`key`)
);
diff --git a/pastebin.go b/pastebin.go
index b1b035d..40fa488 100644
--- a/pastebin.go
+++ b/pastebin.go
@@ -30,25 +30,30 @@ import (
// For url routing
"github.com/gorilla/mux"
+ // securecookie for cookie handling
+ "github.com/gorilla/securecookie"
+ // bcrypt for password hashing
+ "golang.org/x/crypto/bcrypt"
)
// Configuration struct,
type Configuration struct {
- Address string `json:"address"` // Url to to the pastebin
- DBHost string `json:"dbhost"` // Name of your database host
- DBName string `json:"dbname"` // Name of your database
- DBPassword string `json:"dbpassword"` // The password for the database user
- DBPlaceHolder [6]string // ? / $[i] Depending on db driver.
- DBPort string `json:"dbport"` // Port of the database
- DBTable string `json:"dbtable"` // Name of the table in the database
- DBType string `json:"dbtype"` // Type of database
- DBUser string `json:"dbuser"` // The database user
- DisplayName string `json:"displayname"` // Name of your pastebin
- GoogleAPIKey string `json:"googleapikey"` // Your google api key
- Highlighter string `json:"highlighter"` // The name of the highlighter.
- ListenAddress string `json:"listenaddress"` // Address that pastebin will bind on
- ListenPort string `json:"listenport"` // Port that pastebin will listen on
- ShortUrlLength int `json:"shorturllength,string"` // Length of the generated short urls
+ Address string `json:"address"` // Url to to the pastebin
+ DBHost string `json:"dbhost"` // Name of your database host
+ DBName string `json:"dbname"` // Name of your database
+ DBPassword string `json:"dbpassword"` // The password for the database user
+ DBPlaceHolder [7]string // ? / $[i] Depending on db driver.
+ DBPort string `json:"dbport"` // Port of the database
+ DBTable string `json:"dbtable"` // Name of the table in the database
+ DBAccountsTable string `json:"dbaccountstable"` // Name of the table in the database
+ DBType string `json:"dbtype"` // Type of database
+ DBUser string `json:"dbuser"` // The database user
+ DisplayName string `json:"displayname"` // Name of your pastebin
+ GoogleAPIKey string `json:"googleapikey"` // Your google api key
+ Highlighter string `json:"highlighter"` // The name of the highlighter.
+ ListenAddress string `json:"listenaddress"` // Address that pastebin will bind on
+ 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.
@@ -70,14 +75,15 @@ type Response struct {
// This struct is used for indata when a request is being made to the pastebin.
type Request struct {
- DelKey string `json:"delkey"` // The delkey that is used to delete paste
- Expiry int64 `json:"expiry,string"` // An expiry date
- Id string `json:"id"` // The id of the paste
- Lang string `json:"lang"` // The language of the paste
- Paste string `json:"paste"` // The actual pase
- Style string `json:"style"` // The style of the paste
- Title string `json:"title"` // The title of the paste
- WebReq bool `json:"webreq"` // If its a webrequest or not
+ DelKey string `json:"delkey"` // The delkey that is used to delete paste
+ Expiry int64 `json:"expiry,string"` // An expiry date
+ Id string `json:"id"` // The id of the paste
+ Lang string `json:"lang"` // The language of the paste
+ Paste string `json:"paste"` // The actual pase
+ Style string `json:"style"` // The style of the paste
+ Title string `json:"title"` // The title of the paste
+ 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.
@@ -98,11 +104,18 @@ type Page struct {
UrlHome string
UrlRaw string
WrapperErr string
+ UserKey string
+}
+type Pastes struct {
+ Response []Response
}
// Template pages,
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*
var configuration Configuration
@@ -113,6 +126,12 @@ var listOfLangsFirst map[string]string
var listOfLangsLast 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,
//
@@ -261,7 +280,7 @@ func checkArgs() {
func getDBHandle() *sql.DB {
var dbinfo string
- for i := 0; i < 6; i++ {
+ for i := 0; i < 7; i++ {
configuration.DBPlaceHolder[i] = "?"
}
@@ -280,7 +299,7 @@ func getDBHandle() *sql.DB {
configuration.DBUser,
configuration.DBPassword,
configuration.DBName)
- for i := 0; i < 6; i++ {
+ for i := 0; i < 7; i++ {
configuration.DBPlaceHolder[i] = "$" + strconv.Itoa(i+1)
}
@@ -365,13 +384,14 @@ func shaPaste(paste string) string {
// paste, the actual paste data as a string,
// expiry, the epxpiry date in epoch time as an int64
// 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
// Escape user input,
paste = html.EscapeString(paste)
title = html.EscapeString(title)
+ user_key = html.EscapeString(user_key)
// Hash paste data and query database to see if paste exists
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,
var dbQuery string
- for i := 0; i < 6; i++ {
+ for i := 0; i < 7; i++ {
dbQuery += configuration.DBPlaceHolder[i] + ","
}
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)
- _, err = stmt.Exec(id, title, sha, paste, delKey, expiry)
+ _, err = stmt.Exec(id, title, sha, paste, delKey, expiry, user_key)
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",
@@ -453,6 +473,9 @@ func savePaste(title string, paste string, expiry int64) Response {
func DelHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
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"]
@@ -516,7 +539,7 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
return
}
- p := savePaste(inData.Title, inData.Paste, inData.Expiry)
+ p := savePaste(inData.Title, inData.Paste, inData.Expiry, inData.UserKey)
d, _ = json.MarshalIndent(p, "DEBUG : ", " ")
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),
PasteTitle: "Copy of " + p.Title,
Title: "Copy of " + p.Title,
+ UserKey: getUserKey(r),
}
err := templates.ExecuteTemplate(w, "index.html", page)
@@ -835,6 +859,209 @@ func RawHandler(w http.ResponseWriter, r *http.Request) {
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
func RootHandler(w http.ResponseWriter, r *http.Request) {
@@ -843,6 +1070,7 @@ func RootHandler(w http.ResponseWriter, r *http.Request) {
LangsLast: listOfLangsLast,
Title: configuration.DisplayName,
UrlAddress: configuration.Address,
+ UserKey: getUserKey(r),
}
err := templates.ExecuteTemplate(w, "index.html", p)
@@ -906,6 +1134,10 @@ func main() {
router.HandleFunc("/raw/{pasteId}", RawHandler).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("/assets/pastebin.css", serveCss).Methods("GET")