Browse Source

add method for generating an API Token

master
forest 8 months ago
parent
commit
2de155e0ee
8 changed files with 127 additions and 28 deletions
  1. +1
    -1
      backend.go
  2. +11
    -0
      db_model.go
  3. +32
    -0
      frontend.go
  4. +48
    -26
      frontend/profile.gotemplate.html
  5. +31
    -1
      frontend/static/greenhouse.css
  6. +1
    -0
      go.mod
  7. +2
    -0
      go.sum
  8. +1
    -0
      schema_versions/02_up_create_tenants_etc.sql

+ 1
- 1
backend.go View File

@ -375,7 +375,7 @@ func getBillingTimeInfo() (int, int, time.Time, time.Time, float64) {
log.Printf("%+v\n", err)
panic(err)
}
startOfBillingMonth := time.Date(billingYear, time.Month(billingMonth), 1, 0, 0, 0, 1, utcLocation)
startOfBillingMonth := time.Date(billingYear, time.Month(billingMonth), 1, 0, 0, 0, 0, utcLocation)
endOfBillingMonth := time.Date(nextBillingYear, time.Month(nextBillingMonth), 0, 23, 59, 59, int(int64(time.Second))-1, utcLocation)
//TODO
monthDuration := float64(int64(endOfBillingMonth.Sub(startOfBillingMonth)))


+ 11
- 0
db_model.go View File

@ -357,6 +357,17 @@ func (model *DBModel) SetReservedPorts(tenantId, portStart, portEnd, portBucket
return nil
}
func (model *DBModel) SetHashedAPIToken(tenantId int, hashedAPIToken string) error {
_, err := model.DB.Exec(
"UPDATE tenants SET hashed_api_token = $1, WHERE id = $2",
hashedAPIToken, tenantId,
)
if err != nil {
return errors.Wrap(err, "SetHashedAPIToken(): ")
}
return nil
}
func (model *DBModel) GetNextReservedPorts() (int, int, int, error) {
port := 0
bucket := 0


+ 32
- 0
frontend.go View File

@ -27,6 +27,7 @@ import (
"time"
"github.com/gorilla/mux"
base58 "github.com/shengdoushi/base58"
chart "github.com/wcharczuk/go-chart/v2"
chartdrawing "github.com/wcharczuk/go-chart/v2/drawing"
)
@ -324,8 +325,38 @@ func initFrontend(workingDirectory string, config *Config, model *DBModel, backe
return
}
apiToken := (*session.Flash)["api-token"]
if request.Method == "POST" {
postedHashOfSessionId := request.PostFormValue("hashOfSessionId")
if postedHashOfSessionId != hashOfSessionId {
app.setFlash(responseWriter, session, "error", "anti-CSRF validation failed\n")
http.Redirect(responseWriter, request, "/profile", http.StatusFound)
return
}
action := request.PostFormValue("action")
if action == "reload_api_token" {
apiTokenBuffer := make([]byte, 16)
rand.Read(apiTokenBuffer)
apiToken := base58.Encode(apiTokenBuffer, base58.BitcoinAlphabet)
rawHash := sha256.Sum256([]byte(apiToken))
hashedAPIToken := fmt.Sprintf("%x", rawHash)
err := app.Model.SetHashedAPIToken(session.TenantId, hashedAPIToken)
if err != nil {
app.unhandledError(responseWriter, err)
return
}
app.setFlash(responseWriter, session, "api-token", apiToken)
app.setFlash(responseWriter, session, "info", fmt.Sprintf("Success! Your new API Token is %s. It will not be displayed again, so make sure to copy and paste it or write it down now!\n", apiToken))
} else {
app.setFlash(responseWriter, session, "error", "unknown action\n")
}
http.Redirect(responseWriter, request, "/profile", http.StatusFound)
return
}
data := struct {
Subdomain string
APIToken string
BytesSoFar string
BillingAlarmSMS string
BillingAlarmEmail string
@ -334,6 +365,7 @@ func initFrontend(workingDirectory string, config *Config, model *DBModel, backe
HashOfSessionId string
}{
Subdomain: tenant.Subdomain,
APIToken: apiToken,
BytesSoFar: ByteCountSI(usageTotal),
BillingAlarmSMS: tenant.SMSAlarmNumber,
BillingAlarmEmail: tenant.Email,


+ 48
- 26
frontend/profile.gotemplate.html View File

@ -1,7 +1,7 @@
<form class="horizontal wrap justify-center">
<div class="horizontal wrap justify-center">
<div class="tab-container three-tabs">
@ -9,17 +9,39 @@
<label class="tab" for="account-status">account status</label>
<div class="vertical tab-content">
<img src="/profile/usage_graph.png"/>
<div class="horizontal align-center">
<p>
{{ .BytesSoFar }} used this month
</p>
<p>
{{ .BytesSoFar }} used this month
</p>
<div class="horizontal justify-right align-center margin-bottom">
<form method="POST" action="#">
<label for="api-token">API Token: </label>
{{ if .APIToken }}
<span class="fake-text-input">{{ .APIToken }}</span>
{{ else }}
<span class="fake-text-input">••••••••••••••</span>
{{ end }}
<input type="hidden" name="hashOfSessionId" value="{{ .HashOfSessionId }}"/>
<input type="hidden" name="action" value="reload_api_token"/>
<input type="image" name="submit" src="/static/reload.svg" alt="Submit" class="reload-api-token-button" />
</form>
</div>
<div class="horizontal justify-right align-center margin-bottom">
<span class="fine-print">
{{ if .APIToken }}
This API Token will not be displayed again, <br/>
so make sure to copy and paste it or write it down now!
{{ else }}
Click the refresh button to generate a new API Token
{{ end }}
</span>
</div>
</div>
<input type="radio" name="account" value="card" id="account-card"></input>
<label class="tab" for="account-card">debit / credit</label>
<div class="vertical tab-content">
<div class="horizontal align-center">
<div class="horizontal align-center margin-bottom">
<p>
Creedeet card
</p>
@ -29,7 +51,7 @@
<input type="radio" name="account" value="crypto" id="account-crypto"></input>
<label class="tab" for="account-crypto">cryptocurrency</label>
<div class="vertical tab-content">
<div class="horizontal align-center">
<div class="horizontal align-center margin-bottom">
<p>
dogs
</p>
@ -42,11 +64,11 @@
<input type="radio" name="domain" value="free" id="domain-free" checked="checked"></input>
<label class="tab" for="domain-free">free subdomain</label>
<div class="tab-content vertical">
<div class="horizontal align-center">
<div class="horizontal align-center margin-bottom">
<input class="short right-align" type="text" name="subdomain" placeholder="subdomain" value="{{ .Subdomain }}"></input>
<span>&nbsp;.greenhouseusers.com</span>
</div>
<div class="horizontal justify-right">
<div class="horizontal justify-right ">
<input type="submit" value="Update"></input>
</div>
</div>
@ -54,7 +76,7 @@
<input type="radio" name="domain" value="managed" id="domain-managed"></input>
<label class="tab" for="domain-managed">managed domain</label>
<div class="vertical tab-content">
<div class="horizontal align-center">
<div class="horizontal align-center margin-bottom">
<p>
managed domain!
</p>
@ -64,7 +86,7 @@
<input type="radio" name="domain" value="external" id="domain-external"></input>
<label class="tab" for="domain-external">external domains</label>
<div class="vertical tab-content">
<div class="horizontal align-center">
<div class="horizontal align-center margin-bottom">
<p>
external domain!
</p>
@ -76,42 +98,42 @@
<input type="radio" name="mode" value="what-you-use" id="mode-what-you-use" checked="checked"></input>
<label class="tab" for="mode-what-you-use">pay for what you use</label>
<div class="vertical tab-content">
<div class="horizontal align-center justify-left">
<div class="horizontal align-center justify-left margin-bottom">
<b>Billing Alarm:</b>
</div>
<div class="horizontal justify-right align-center">
<div class="horizontal justify-right align-center margin-bottom">
<label for="billing-alarm-threshold">Threshold: </label>
<span class="money-unit">$</span>
<input name="billingAlarmThreshold" id="billing-alarm-threshold" placeholder="notify after $_.__/mo"
type="number" min="0.01" step="0.01" value="{{ .BillingAlarmThreshold }}"></input>
</div>
<div class="horizontal justify-right align-center">
<div class="horizontal justify-right align-center margin-bottom">
<span class="fine-print">you will be notified when your bill reaches this amount in a given month.</span>
</div>
<div class="horizontal justify-right align-center">
<div class="horizontal justify-right align-center margin-bottom">
<label for="billing-alarm-sms">SMS: </label>
<input name="billingAlarmSMS" id="billing-alarm-sms" placeholder="phone number (optional)"
type="string" value="{{ .BillingAlarmSMS }}"></input>
</div>
<div class="horizontal justify-right align-center">
<div class="horizontal justify-right align-center margin-bottom">
<label for="billing-alarm-email">Email: </label>
<input name="billingAlarmEmail" id="billing-alarm-email" placeholder="you@youremail.com"
type="string" value="{{ .BillingAlarmEmail }}"></input>
</div>
<div class="horizontal align-center justify-left">
<div class="horizontal align-center justify-left margin-bottom">
<b>Service Limit:</b>
</div>
<div class="horizontal justify-right align-center">
<div class="horizontal justify-right align-center margin-bottom">
<span class="money-unit">$</span class="money-unit">
<input name="billingLimit" placeholder="suspend after $_.__/mo"
type="number" min="0.5" step="0.01" value="{{ .BillingLimit }}"></input>
</div>
<div class="horizontal justify-right align-center">
<div class="horizontal justify-right align-center margin-bottom">
<span class="fine-print">
you will never be charged more than this amount per month; <br/>
the service will suspend itself instead of charging you more.</span>
</div>
<div class="horizontal justify-right">
<div class="horizontal justify-right margin-bottom">
<input type="submit" value="Update"></input>
</div>
</div>
@ -119,7 +141,7 @@
<input type="radio" name="mode" value="dedicated" id="mode-dedicated"></input>
<label class="tab" for="mode-dedicated">dedicated server</label>
<div class="vertical tab-content">
<div class="horizontal align-center">
<div class="horizontal align-center margin-bottom">
<p>
dedicated server!
</p>
@ -132,13 +154,13 @@
<div>
<ul>
<li>Use with any number of servers / domains</li>
<li>Primary supported protocols: HTTPS, anything-over-TLS</li>
<li>Primary supported protocols: HTTP, HTTPS, anything-over-TLS</li>
<li>20 Arbitrarily-assigned static TCP ports for use with SSH & other legacy protocols</li>
<li>UDP is currently not supported, but we can probably add it if you need it!</li>
</ul>
<p>
<b>Free Tier:</b> usage below 1 GB is free for the first 3 months, no credit/debit card required. <br/>
Once you consume 1 GB of bandwidth or 3 months elapse since opening the account, <br/>
<b>Free Tier:</b> usage below 3 GB is free for the first 3 months, no credit/debit card required. <br/>
Once you consume 3 GB of bandwidth or 3 months elapse since opening the account, <br/>
service will be suspended until you add a payment method.
</p>
<p>
@ -151,8 +173,8 @@
</p>
<p>
If you want to host an email server, you will need to choose the "dedicated server" option, <br/>
because the email protocol (SMTP) <a href="https://greenhouse.server.garden/how-it-works">requires a dedicated IP address for each domain</a>.
because the email protocol (SMTP) <a href="https://greenhouse.server.garden/how-it-works#email">requires a dedicated IP address for each domain</a>.
</p>
</div>
</div>
</form>
</div>

+ 31
- 1
frontend/static/greenhouse.css View File

@ -143,11 +143,13 @@ pre.flash.info {
}
.horizontal {
width: 100%;
display: flex;
align-items: flex-start;
}
.margin-bottom {
margin-bottom: 0.4em;
}
.align-center {
align-items: center;
}
@ -191,6 +193,34 @@ input[type=number] {
-moz-appearance: textfield;
}
/* roughly copy and pasted from the firefox user agent style sheet on linux :D */
.fake-text-input {
text-rendering: auto;
color: rgb(58, 58, 58);
display: inline-block;
text-align: start;
-webkit-appearance: textfield;
background-color: white;
margin: 0em;
font: 400 13.3333px Arial;
padding: 7px;
border-radius: 3px;
box-shadow: inset 0 0 3px 0px rgba(0,0,0,0.1);
border: 1px solid #ccc;
min-width: 18em;
}
.reload-api-token-button {
border-radius: 100%;
border: 2px solid #ccc;
display: inline-block;
width: 1.3em;
padding: 0.3em;
position: relative;
top: 0.68em;
margin-top: -0.68em;
}
label {
margin-right: 1em;
}


+ 1
- 0
go.mod View File

@ -9,6 +9,7 @@ require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/lib/pq v1.10.0 // indirect
github.com/shengdoushi/base58 v1.0.0 // indirect
github.com/wcharczuk/go-chart v2.0.1+incompatible // indirect
github.com/wcharczuk/go-chart/v2 v2.1.0 // indirect
github.com/xhit/go-simple-mail v2.2.2+incompatible // indirect


+ 2
- 0
go.sum View File

@ -12,6 +12,8 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/shengdoushi/base58 v1.0.0 h1:tGe4o6TmdXFJWoI31VoSWvuaKxf0Px3gqa3sUWhAxBs=
github.com/shengdoushi/base58 v1.0.0/go.mod h1:m5uIILfzcKMw6238iWAhP4l3s5+uXyF3+bJKUNhAL9I=
github.com/wcharczuk/go-chart v2.0.1+incompatible h1:0pz39ZAycJFF7ju/1mepnk26RLVLBCWz1STcD3doU0A=
github.com/wcharczuk/go-chart v2.0.1+incompatible/go.mod h1:PF5tmL4EIx/7Wf+hEkpCqYi5He4u90sw+0+6FhrryuE=
github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I=


+ 1
- 0
schema_versions/02_up_create_tenants_etc.sql View File

@ -3,6 +3,7 @@ CREATE TABLE tenants (
email TEXT NOT NULL UNIQUE,
subdomain TEXT UNIQUE,
hashed_password TEXT NOT NULL,
hashed_api_token TEXT NOT NULL,
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
lax_cookie BOOLEAN NOT NULL DEFAULT TRUE,
sms_alarm_number TEXT NULL,


Loading…
Cancel
Save