Gregory Rudolph
4 years ago
11 changed files with 447 additions and 3 deletions
@ -0,0 +1,118 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"math/rand" |
||||||
|
"net/http" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/gorilla/mux" |
||||||
|
) |
||||||
|
|
||||||
|
func reqPass(w http.ResponseWriter, r *http.Request) { |
||||||
|
username := r.FormValue("UserName") |
||||||
|
ipaddr := r.Header.Get("X-Real-IP") |
||||||
|
log.LogInfo(fmt.Sprintf("reqPass called:```username: %s\nip : %s```", username, ipaddr)) |
||||||
|
sendPassword(username, ipaddr) |
||||||
|
http.Redirect(w, r, "/login", 302) |
||||||
|
} |
||||||
|
|
||||||
|
func tryLogin(w http.ResponseWriter, r *http.Request) { |
||||||
|
session, err := store.Get(r, "2fa") |
||||||
|
if err != nil { |
||||||
|
log.LogWarn("Error opening session for 2fa store") |
||||||
|
} |
||||||
|
vars := mux.Vars(r) |
||||||
|
username := vars["username"] |
||||||
|
password := vars["password"] |
||||||
|
ip := r.Header.Get("X-Real-IP") |
||||||
|
if len(username) == 0 { |
||||||
|
username = r.FormValue("UserName") |
||||||
|
password = r.FormValue("TempPass") |
||||||
|
} |
||||||
|
access, _ := detectUser(r, "tryLogin") |
||||||
|
if !access { |
||||||
|
log.LogDebug(fmt.Sprintf("%s is attempting login", getSessionIdentifier(r))) |
||||||
|
access = usePassword(username, password, ip) |
||||||
|
if access { |
||||||
|
log.LogInfo(fmt.Sprintf("%s has successfully logged in from %s", username, ip)) |
||||||
|
log.LogDebug(fmt.Sprintf("```%+v```", session.Values)) |
||||||
|
session.Values["username"] = username |
||||||
|
session.Values["ip"] = ip |
||||||
|
session.Values["timestamp"] = fmt.Sprintf("%+v", time.Now()) |
||||||
|
err = session.Save(r, w) |
||||||
|
if err != nil { |
||||||
|
log.LogWarn(fmt.Sprintf("Error saving cookie. ```%+v```", err)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fmt.Fprintf(w, "<br><p>Login Success: %+v</p><br>Session: %+v", access, session.Values) |
||||||
|
} |
||||||
|
|
||||||
|
func usePassword(user string, pass string, ip string) bool { |
||||||
|
tok := toks[user] |
||||||
|
delete(toks, user) |
||||||
|
if time.Since(tok.timestamp) > (time.Minute * 5) { |
||||||
|
log.LogWarn(fmt.Sprintf("%s attempted to use expired token.", user)) |
||||||
|
return false |
||||||
|
} |
||||||
|
if tok.ip != ip { |
||||||
|
log.LogWarn(fmt.Sprintf("%s attempted to use an improper IP.", user)) |
||||||
|
return false |
||||||
|
} |
||||||
|
if tok.password != pass { |
||||||
|
log.LogWarn(fmt.Sprintf("%s attempted to use an improper password. %s vs %s", user, tok.password, pass)) |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
func genPassword(length int) string { |
||||||
|
rand.Seed(time.Now().UnixNano()) |
||||||
|
chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + |
||||||
|
"abcdefghijklmnopqrstuvwxyz" + |
||||||
|
"0123456789") |
||||||
|
var b strings.Builder |
||||||
|
for i := 0; i < length; i++ { |
||||||
|
b.WriteRune(chars[rand.Intn(len(chars))]) |
||||||
|
} |
||||||
|
return b.String() // E.g. "ExcbsVQs"
|
||||||
|
} |
||||||
|
func sendPassword(user string, ipaddr string) { |
||||||
|
str := genPassword(8) |
||||||
|
m, _ := dg.GuildMember(config.GuildID, user) |
||||||
|
toks[m.User.Username] = tokens{ |
||||||
|
username: user, |
||||||
|
ip: ipaddr, |
||||||
|
password: str, |
||||||
|
timestamp: time.Now(), |
||||||
|
} |
||||||
|
pmChann, _ := dg.UserChannelCreate(user) |
||||||
|
dg.ChannelMessageSend(pmChann.ID, fmt.Sprintf("A temporary password was requested from %s:", ipaddr)) |
||||||
|
dg.ChannelMessageSend(pmChann.ID, fmt.Sprintf("```%s```", str)) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
func getSessionIdentifier(r *http.Request) string { |
||||||
|
ipaddr := r.Header.Get("X-Real-IP") |
||||||
|
if ipaddr == "" { |
||||||
|
ipaddr = r.RemoteAddr |
||||||
|
} |
||||||
|
uri := r.URL.Path |
||||||
|
return fmt.Sprintf("%s:%s", ipaddr, uri) |
||||||
|
} |
||||||
|
|
||||||
|
func detectUser(r *http.Request, callFunc string) (bool, string) { |
||||||
|
log.LogInfo(fmt.Sprintf("%s called detectUser", getSessionIdentifier(r))) |
||||||
|
session, err := store.Get(r, "2fa") |
||||||
|
if err != nil { |
||||||
|
log.LogDebug(fmt.Sprintf("Unable to open 2fa session in %s", callFunc)) |
||||||
|
} |
||||||
|
if session.Values["username"] != nil { |
||||||
|
return true, fmt.Sprintf("%s", session.Values["username"]) |
||||||
|
} |
||||||
|
return false, "" |
||||||
|
} |
@ -0,0 +1,137 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
"io/ioutil" |
||||||
|
"net/http" |
||||||
|
"os" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/gorilla/mux" |
||||||
|
"github.com/gorilla/sessions" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY"))) |
||||||
|
toks = make(map[string]tokens) |
||||||
|
acctLinks = make(map[string]linkedAccount) |
||||||
|
) |
||||||
|
|
||||||
|
func topWrapper(r *http.Request) string { |
||||||
|
log.LogInfo(fmt.Sprintf("%s called topWrapper", getSessionIdentifier(r))) |
||||||
|
headerTemplate, err := ioutil.ReadFile("./static/header.tpl") |
||||||
|
if err != nil { |
||||||
|
log.LogError(fmt.Sprintf("Unable to open header template: ```%+v```", err)) |
||||||
|
return "" |
||||||
|
} |
||||||
|
header := string(headerTemplate) |
||||||
|
login := "Login" |
||||||
|
loggedIn, user := detectUser(r, "topWrapper") |
||||||
|
if loggedIn { |
||||||
|
login = fmt.Sprintf("Logout %s", user) |
||||||
|
} |
||||||
|
header = strings.Replace(header, "$LOGIN", login, -1) |
||||||
|
return header |
||||||
|
} |
||||||
|
|
||||||
|
func bodyWrapper(r *http.Request, template string) string { |
||||||
|
log.LogInfo(fmt.Sprintf("%s called bodyWrapper", getSessionIdentifier(r))) |
||||||
|
bodyTemplate, err := ioutil.ReadFile(fmt.Sprintf("./static/%+v.tpl", template)) |
||||||
|
if err != nil { |
||||||
|
log.LogError(fmt.Sprintf("Attempt to load %s.tpl failed. ```%+v```", template, err)) |
||||||
|
return bodyWrapper(r, "404") |
||||||
|
} |
||||||
|
return string(bodyTemplate) |
||||||
|
|
||||||
|
} |
||||||
|
func pageBuilder(r *http.Request, pageName string) string { |
||||||
|
pageCode := topWrapper(r) |
||||||
|
pageCode += bodyWrapper(r, pageName) |
||||||
|
return pageCode |
||||||
|
} |
||||||
|
|
||||||
|
func greetUser(w http.ResponseWriter, r *http.Request) { |
||||||
|
log.LogInfo(fmt.Sprintf("%s called greetUser", getSessionIdentifier(r))) |
||||||
|
loggedIn, username := detectUser(r, "greetUser") |
||||||
|
fmt.Fprintf(w, pageBuilder(r, "home")) |
||||||
|
if loggedIn { |
||||||
|
fmt.Fprintf(w, strings.Replace(bodyWrapper(r, "loggedIn"), "$USER", username, -1)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func passPage(w http.ResponseWriter, r *http.Request) { |
||||||
|
log.LogInfo(fmt.Sprintf("%s called passPage", getSessionIdentifier(r))) |
||||||
|
fmt.Fprintf(w, pageBuilder(r, "pass")) |
||||||
|
} |
||||||
|
func loginPage(w http.ResponseWriter, r *http.Request) { |
||||||
|
log.LogInfo(fmt.Sprintf("%s called loginPage", getSessionIdentifier(r))) |
||||||
|
session, err := store.Get(r, "2fa") |
||||||
|
if err != nil { |
||||||
|
log.LogWarn("Unable to open 2fa session in loginpage") |
||||||
|
} |
||||||
|
loggedIn, _ := detectUser(r, "loginPage") |
||||||
|
if loggedIn { |
||||||
|
session.Values["username"] = nil |
||||||
|
err = session.Save(r, w) |
||||||
|
if err != nil { |
||||||
|
log.LogWarn("Error logging out from loginPage()") |
||||||
|
} |
||||||
|
fmt.Fprintf(w, pageBuilder(r, "home")) |
||||||
|
return |
||||||
|
} |
||||||
|
fmt.Fprintf(w, pageBuilder(r, "login")) |
||||||
|
} |
||||||
|
|
||||||
|
func notFoundPage(w http.ResponseWriter, r *http.Request) { |
||||||
|
go log.LogWarn(fmt.Sprintf("%s triggered notFoundPage", getSessionIdentifier(r))) |
||||||
|
fmt.Fprintf(w, topWrapper(r)) |
||||||
|
|
||||||
|
fmt.Fprintf(w, card("Oops! That Page Was Not found.", |
||||||
|
"Sorry, a 404 error has occured. The requested page not found! <br><br>"+ |
||||||
|
"<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/t3otBjVZzT0\" frameborder=\"0\" allow=\"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>", |
||||||
|
"<div class=\"error-actions\"><a href=\"/\" class=\"btn btn-primary btn-lg\"><span class=\"glyphicon glyphicon-home\"></span>Take Me Home </a> <a href=\"mailto://rudi@nmare.net\" class=\"btn btn-default btn-lg\"><span class=\"glyphicon glyphicon-envelope\"></span> Contact Support </a></div>")) |
||||||
|
|
||||||
|
} |
||||||
|
func card(title string, content string, footer string) string { |
||||||
|
cardTemplate, err := ioutil.ReadFile("./static/card.tpl") |
||||||
|
if err != nil { |
||||||
|
log.LogError("Unable to open card template") |
||||||
|
return "" |
||||||
|
} |
||||||
|
cardString := string(cardTemplate) |
||||||
|
cardString = strings.Replace(cardString, "$TITLE", title, -1) |
||||||
|
cardString = strings.Replace(cardString, "$CONTENT", content, -1) |
||||||
|
cardString = strings.Replace(cardString, "$FOOTER", footer, -1) |
||||||
|
return cardString |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
func getPending(w http.ResponseWriter, r *http.Request) { |
||||||
|
loggedIn, _ := detectUser(r, "getPending") |
||||||
|
if loggedIn { |
||||||
|
pending, err := json.Marshal(config.Verifications) |
||||||
|
if err != nil { |
||||||
|
log.LogErrorType(err) |
||||||
|
notFoundPage(w, r) |
||||||
|
} |
||||||
|
fmt.Fprintf(w, string(pending)) |
||||||
|
} else { |
||||||
|
notFoundPage(w, r) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func runWeb() { |
||||||
|
router := mux.NewRouter().StrictSlash(true) |
||||||
|
log.LogInfo("Adding HandleFuncs to router") |
||||||
|
router.NotFoundHandler = http.HandlerFunc(notFoundPage) |
||||||
|
router.HandleFunc("/pass", passPage) |
||||||
|
router.HandleFunc("/login", loginPage) |
||||||
|
router.HandleFunc("/api/login", tryLogin) |
||||||
|
router.HandleFunc("/api/pending", getPending) |
||||||
|
router.HandleFunc("/api/passreq", reqPass) |
||||||
|
router.HandleFunc("/", greetUser) |
||||||
|
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static")))) |
||||||
|
log.LogInfo("Starting server") |
||||||
|
log.LogErrorType(http.ListenAndServe(":8080", router)) |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
<div class="container"> |
||||||
|
<div class="row"> |
||||||
|
<div class="col-md-12"> |
||||||
|
<div class="error-template"> |
||||||
|
<h1> |
||||||
|
Oops!</h1><br> |
||||||
|
<h2> |
||||||
|
404 Not Found</h2> |
||||||
|
<div class="error-details"> |
||||||
|
Sorry, an error has occured, Requested page not found! |
||||||
|
</div> |
||||||
|
<br> |
||||||
|
<div class="error-actions"> |
||||||
|
<a href="/" class="btn btn-primary btn-lg"><span class="glyphicon glyphicon-home"></span> |
||||||
|
Take Me Home </a> <a href="mailto://rudi@nmare.net" class="btn btn-default btn-lg"><span class="glyphicon glyphicon-envelope"></span> Contact Support </a> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<br> |
||||||
|
</div> |
@ -0,0 +1,21 @@ |
|||||||
|
<div class="container"> |
||||||
|
<div class="row"> |
||||||
|
<div class="col-md-12"> |
||||||
|
<div class="error-template"> |
||||||
|
<h1> |
||||||
|
Oops!</h1><br> |
||||||
|
<h2>Something has gone HORRIBLY wrong! Congrats!</h2> |
||||||
|
<div class="error-details"> |
||||||
|
Not really sorry, but an error has occured. 500 means something went wrong on the server side, but.. Let's be honest. It was really a user malfunction. |
||||||
|
</div> |
||||||
|
<br> |
||||||
|
<div class="error-actions"> |
||||||
|
<a href="/" class="btn btn-primary btn-lg"><span class="glyphicon glyphicon-home"></span> |
||||||
|
Take Me Home </a> <a href="mailto://rudi@nmare.net" class="btn btn-default btn-lg"><span class="glyphicon glyphicon-envelope"></span> Contact Support </a> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<br> |
||||||
|
<iframe width="560" height="315" src="https://www.youtube.com/embed/t3otBjVZzT0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> |
||||||
|
</div> |
@ -0,0 +1,11 @@ |
|||||||
|
<br><br> |
||||||
|
<div class="container h-100 d-flex justify-content-center align-items-center"> |
||||||
|
|
||||||
|
<div class="card" style="width: 50rem;"> |
||||||
|
<div class="card-body col"> |
||||||
|
<h2 class="card-title">$TITLE</h1> |
||||||
|
<p class="card-text">$CONTENT</p> |
||||||
|
<p class="card-text">$FOOTER</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
@ -0,0 +1,41 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>Thanos</title> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"> |
||||||
|
<link rel="icon" type="image/png" href="https://lh6.googleusercontent.com/BvczCCYhJUsKIs3dsowl1vuvnBtCGSDcMDekt5PehwQk3cQLfHkEn80cR3IuMxUFmd5Sh_UQ=w16383"> |
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> |
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script> |
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script> |
||||||
|
|
||||||
|
<!-- Navigation --> |
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top"> |
||||||
|
<div class="container"> |
||||||
|
<a class="navbar-brand" href="/"> |
||||||
|
<img src="" alt=""> |
||||||
|
</a> |
||||||
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"> |
||||||
|
<span class="navbar-toggler-icon"></span> |
||||||
|
</button> |
||||||
|
<div class="collapse navbar-collapse" id="navbarResponsive"> |
||||||
|
<ul class="navbar-nav ml-auto"> |
||||||
|
<li class="nav-item active"> |
||||||
|
<a class="nav-link" href="/">Home |
||||||
|
<span class="sr-only">(current)</span> |
||||||
|
</a> |
||||||
|
</li> |
||||||
|
<li class="nav-item"> |
||||||
|
<a class="nav-link" href="/pass">Request Token</a> |
||||||
|
</li> |
||||||
|
<li class="nav-item"> |
||||||
|
<a class="nav-link" href="/login">$LOGIN</a> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</nav> |
||||||
|
|
||||||
|
|
||||||
|
</head> |
@ -0,0 +1,15 @@ |
|||||||
|
<br><br> |
||||||
|
<div class="container h-100 d-flex justify-content-center align-items-center"> |
||||||
|
|
||||||
|
|
||||||
|
<div class="container h-100 d-flex justify-content-center align-items-center"> |
||||||
|
|
||||||
|
<div class="card" style="width: 50rem;"> |
||||||
|
<div class="card-body col"> |
||||||
|
<h2 class="card-title">Cookie Policy</h1> |
||||||
|
<p class="card-text">What website doesn't use cookies nowadays?</p> |
||||||
|
<p class="card-text">This website does not use any 3rd party cookies. All cookies are encrypted and only used by this website. </p> |
||||||
|
<p class="card-text">If you are actually reading this, chances are your data isn't valuable enough for me to care about tracking you.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
@ -0,0 +1,31 @@ |
|||||||
|
<div class="container-fluid"> |
||||||
|
<div class="row no-gutter"> |
||||||
|
<div class="d-none d-md-flex col-md-4 col-lg-6 bg-image"></div> |
||||||
|
<div class="col-md-8 col-lg-6"> |
||||||
|
<div class="login d-flex align-items-center py-5"> |
||||||
|
<div class="container"> |
||||||
|
<div class="row"> |
||||||
|
<div class="col-md-9 col-lg-8 mx-auto"> |
||||||
|
<h3 class="login-heading mb-4">Thanos OTP Login</h3> |
||||||
|
<form action="/api/login"> |
||||||
|
<div class="form-label-group"> |
||||||
|
<input type="text" name="UserName" id="Username" class="form-control" placeholder="Username" required autofocus> |
||||||
|
<label for="inputEmail">Username</label> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="form-label-group"> |
||||||
|
<input type="password" name="TempPass" id="TempPassword" class="form-control" placeholder="Do Not Use Your Discord Password" required> |
||||||
|
<label for="inputPassword">Temporary Password</label> |
||||||
|
</div> |
||||||
|
|
||||||
|
<button class="btn btn-lg btn-primary btn-block btn-login text-uppercase font-weight-bold mb-2" type="submit">Sign in</button> |
||||||
|
<div class="text-center"> |
||||||
|
<a class="small" href="/pass">Need to request a password?</a></div> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
@ -0,0 +1,31 @@ |
|||||||
|
<body> |
||||||
|
<br><br> |
||||||
|
<div class="row h-100 justify-content-center align-items-center"> |
||||||
|
<div class="card"> |
||||||
|
<div class="container"> |
||||||
|
<form action="/api/passreq"> |
||||||
|
<table> |
||||||
|
<tr> |
||||||
|
<td> |
||||||
|
Discord User: |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
<input type="text" name="UserName" value=""> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td> |
||||||
|
</td> |
||||||
|
<td> |
||||||
|
<input type="submit" value="Submit" class="btn btn-primary" style="float: right;"> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
</table> |
||||||
|
</form> |
||||||
|
|
||||||
|
<p>Click the "Submit" button and a temporary password will be sent to the Discord User.</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</body> |
||||||
|
</html> |
Loading…
Reference in new issue