Browse Source

Added UseSSL config option and added deletion button to the recent URLs table

dependabot/npm_and_yarn/web/prismjs-1.21.0
Max Schmitt 8 years ago
parent
commit
96b06a8734
  1. 12
      handlers/public.go
  2. 32
      static/src/Recent/Recent.js
  3. 9
      static/src/Visitors/Visitors.js
  4. 1
      store/store.go
  5. 4
      util/config.go

12
handlers/public.go

@ -1,6 +1,8 @@
package handlers
import (
"crypto/hmac"
"crypto/sha512"
"encoding/base64"
"fmt"
"net/http"
@ -137,6 +139,14 @@ func (h *Handler) handleRecent(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
for k, entry := range entries {
mac := hmac.New(sha512.New, util.GetPrivateKey())
if _, err := mac.Write([]byte(k)); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
entry.DeletionURL = fmt.Sprintf("%s/d/%s/%s", h.getURLOrigin(c), k, url.QueryEscape(base64.RawURLEncoding.EncodeToString(mac.Sum(nil))))
entries[k] = entry
}
c.JSON(http.StatusOK, entries)
}
@ -157,7 +167,7 @@ func (h *Handler) handleDelete(c *gin.Context) {
func (h *Handler) getURLOrigin(c *gin.Context) string {
protocol := "http"
if c.Request.TLS != nil {
if c.Request.TLS != nil || util.GetConfig().UseSSL {
protocol = "https"
}
return fmt.Sprintf("%s://%s", protocol, c.Request.Host)

32
static/src/Recent/Recent.js

@ -1,5 +1,5 @@
import React, { Component } from 'react'
import { Container, Table } from 'semantic-ui-react'
import { Container, Table, Button, Icon } from 'semantic-ui-react'
import toastr from 'toastr'
import Moment from 'react-moment';
@ -9,6 +9,10 @@ export default class RecentComponent extends Component {
}
componentDidMount() {
this.loadRecentURLs()
}
loadRecentURLs() {
fetch('/api/v1/protected/recent', {
method: 'POST',
headers: {
@ -17,13 +21,20 @@ export default class RecentComponent extends Component {
})
.then(res => res.ok ? res.json() : Promise.reject(res.json()))
.then(recent => this.setState({ recent: recent }))
.catch(e => e.done(res => toastr.error(`Could not fetch recent shortened URLs: ${res}`)))
.catch(e => e instanceof Promise ? e.then(error => toastr.error(`Could load recent URLs: ${error.error}`)) : null)
}
onRowClick(id) {
this.props.history.push(`/visitors/${id}`)
}
onEntryDeletion(visit) {
fetch(visit.DeletionURL)
.then(res => res.ok ? res.json() : Promise.reject(res.json()))
.then(() => this.loadRecentURLs())
.catch(e => e instanceof Promise ? e.then(error => toastr.error(`Could not delete: ${error.error}`)) : null)
}
render() {
const { recent } = this.state
return (
@ -35,14 +46,21 @@ export default class RecentComponent extends Component {
<Table.HeaderCell>Created</Table.HeaderCell>
<Table.HeaderCell>Short URL</Table.HeaderCell>
<Table.HeaderCell>All Clicks</Table.HeaderCell>
<Table.HeaderCell>Delete</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{recent && Object.keys(recent).map(key => <Table.Row key={key} title="Click to view visitor statistics" onClick={this.onRowClick.bind(this, key)}>
<Table.Cell>{recent[key].Public.URL}</Table.Cell>
<Table.Cell><Moment>{recent[key].Public.CreatedOn}</Moment></Table.Cell>
<Table.Cell>{`${window.location.origin}/${key}`}</Table.Cell>
<Table.Cell>{recent[key].Public.VisitCount}</Table.Cell>
{recent && Object.keys(recent).map(key => <Table.Row key={key} title="Click to view visitor statistics">
<Table.Cell onClick={this.onRowClick.bind(this, key)}>{recent[key].Public.URL}</Table.Cell>
<Table.Cell onClick={this.onRowClick.bind(this, key)}><Moment>{recent[key].Public.CreatedOn}</Moment></Table.Cell>
<Table.Cell onClick={this.onRowClick.bind(this, key)}>{`${window.location.origin}/${key}`}</Table.Cell>
<Table.Cell onClick={this.onRowClick.bind(this, key)}>{recent[key].Public.VisitCount}</Table.Cell>
<Table.Cell><Button animated='vertical' onClick={this.onEntryDeletion.bind(this, recent[key])}>
<Button.Content hidden>Delete</Button.Content>
<Button.Content visible>
<Icon name='trash' />
</Button.Content>
</Button></Table.Cell>
</Table.Row>)}
</Table.Body>
</Table>

9
static/src/Visitors/Visitors.js

@ -9,7 +9,7 @@ export default class VisitorComponent extends Component {
info: null
}
componentDidMount() {
componentWillMount() {
this.setState({ id: this.props.match.params.id })
fetch("/api/v1/protected/lookup", {
method: "POST",
@ -26,7 +26,11 @@ export default class VisitorComponent extends Component {
.catch(e => {
toastr.error(`Could not fetch lookup: ${e}`)
})
this.loop = setInterval(() => {
this.reloadVisitors()
this.loop = setInterval(this.reloadVisitors, 1000)
}
reloadVisitors = () => {
fetch('/api/v1/protected/visitors', {
method: 'POST',
body: JSON.stringify({
@ -40,7 +44,6 @@ export default class VisitorComponent extends Component {
.then(res => res.ok ? res.json() : Promise.reject(res.json()))
.then(visitors => this.setState({ visitors }))
.catch(e => e.done(res => toastr.error(`Could not fetch visitors: ${res}`)))
}, 1000)
}
componentWillUnmount() {

1
store/store.go

@ -28,6 +28,7 @@ type Store struct {
type Entry struct {
OAuthProvider, OAuthID string
RemoteAddr string `json:",omitempty"`
DeletionURL string `json:",omitempty"`
Public EntryPublicData
}

4
util/config.go

@ -17,6 +17,7 @@ type Configuration struct {
ListenAddr string `yaml:"ListenAddr" env:"LISTEN_ADDR"`
BaseURL string `yaml:"BaseURL" env:"BASE_URL"`
DataDir string `yaml:"DataDir" env:"DATA_DIR"`
UseSSL bool `yaml:"EnableSSL" env:"USE_SSL"`
EnableDebugMode bool `yaml:"EnableDebugMode" env:"ENABLE_DEBUG_MODE"`
ShortedIDLength int `yaml:"ShortedIDLength" env:"SHORTED_ID_LENGTH"`
Google oAuthConf `yaml:"Google" env:"GOOGLE"`
@ -35,6 +36,7 @@ var (
BaseURL: "http://localhost:3000",
DataDir: "data",
EnableDebugMode: false,
UseSSL: false,
ShortedIDLength: 4,
}
)
@ -48,6 +50,8 @@ func ReadInConfig() error {
}
} else if !os.IsNotExist(err) {
return errors.Wrap(err, "could not read config file")
} else {
logrus.Info("No configuration file found, using defaults and environment variable overrides.")
}
if err := config.ApplyEnvironmentConfig(); err != nil {
return errors.Wrap(err, "could not apply environment configuration")

Loading…
Cancel
Save