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. 35
      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 package handlers
import ( import (
"crypto/hmac"
"crypto/sha512"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"net/http" "net/http"
@ -137,6 +139,14 @@ func (h *Handler) handleRecent(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return 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) c.JSON(http.StatusOK, entries)
} }
@ -157,7 +167,7 @@ func (h *Handler) handleDelete(c *gin.Context) {
func (h *Handler) getURLOrigin(c *gin.Context) string { func (h *Handler) getURLOrigin(c *gin.Context) string {
protocol := "http" protocol := "http"
if c.Request.TLS != nil { if c.Request.TLS != nil || util.GetConfig().UseSSL {
protocol = "https" protocol = "https"
} }
return fmt.Sprintf("%s://%s", protocol, c.Request.Host) 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 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 toastr from 'toastr'
import Moment from 'react-moment'; import Moment from 'react-moment';
@ -9,6 +9,10 @@ export default class RecentComponent extends Component {
} }
componentDidMount() { componentDidMount() {
this.loadRecentURLs()
}
loadRecentURLs() {
fetch('/api/v1/protected/recent', { fetch('/api/v1/protected/recent', {
method: 'POST', method: 'POST',
headers: { headers: {
@ -17,13 +21,20 @@ export default class RecentComponent extends Component {
}) })
.then(res => res.ok ? res.json() : Promise.reject(res.json())) .then(res => res.ok ? res.json() : Promise.reject(res.json()))
.then(recent => this.setState({ recent: recent })) .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) { onRowClick(id) {
this.props.history.push(`/visitors/${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() { render() {
const { recent } = this.state const { recent } = this.state
return ( return (
@ -35,14 +46,21 @@ export default class RecentComponent extends Component {
<Table.HeaderCell>Created</Table.HeaderCell> <Table.HeaderCell>Created</Table.HeaderCell>
<Table.HeaderCell>Short URL</Table.HeaderCell> <Table.HeaderCell>Short URL</Table.HeaderCell>
<Table.HeaderCell>All Clicks</Table.HeaderCell> <Table.HeaderCell>All Clicks</Table.HeaderCell>
<Table.HeaderCell>Delete</Table.HeaderCell>
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
<Table.Body> <Table.Body>
{recent && Object.keys(recent).map(key => <Table.Row key={key} title="Click to view visitor statistics" onClick={this.onRowClick.bind(this, key)}> {recent && Object.keys(recent).map(key => <Table.Row key={key} title="Click to view visitor statistics">
<Table.Cell>{recent[key].Public.URL}</Table.Cell> <Table.Cell onClick={this.onRowClick.bind(this, key)}>{recent[key].Public.URL}</Table.Cell>
<Table.Cell><Moment>{recent[key].Public.CreatedOn}</Moment></Table.Cell> <Table.Cell onClick={this.onRowClick.bind(this, key)}><Moment>{recent[key].Public.CreatedOn}</Moment></Table.Cell>
<Table.Cell>{`${window.location.origin}/${key}`}</Table.Cell> <Table.Cell onClick={this.onRowClick.bind(this, key)}>{`${window.location.origin}/${key}`}</Table.Cell>
<Table.Cell>{recent[key].Public.VisitCount}</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.Row>)}
</Table.Body> </Table.Body>
</Table> </Table>

35
static/src/Visitors/Visitors.js

@ -9,7 +9,7 @@ export default class VisitorComponent extends Component {
info: null info: null
} }
componentDidMount() { componentWillMount() {
this.setState({ id: this.props.match.params.id }) this.setState({ id: this.props.match.params.id })
fetch("/api/v1/protected/lookup", { fetch("/api/v1/protected/lookup", {
method: "POST", method: "POST",
@ -26,21 +26,24 @@ export default class VisitorComponent extends Component {
.catch(e => { .catch(e => {
toastr.error(`Could not fetch lookup: ${e}`) toastr.error(`Could not fetch lookup: ${e}`)
}) })
this.loop = setInterval(() => { this.reloadVisitors()
fetch('/api/v1/protected/visitors', { this.loop = setInterval(this.reloadVisitors, 1000)
method: 'POST', }
body: JSON.stringify({
ID: this.props.match.params.id reloadVisitors = () => {
}), fetch('/api/v1/protected/visitors', {
headers: { method: 'POST',
'Authorization': window.localStorage.getItem('token'), body: JSON.stringify({
'Content-Type': 'application/json' ID: this.props.match.params.id
} }),
}) headers: {
.then(res => res.ok ? res.json() : Promise.reject(res.json())) 'Authorization': window.localStorage.getItem('token'),
.then(visitors => this.setState({ visitors })) 'Content-Type': 'application/json'
.catch(e => e.done(res => toastr.error(`Could not fetch visitors: ${res}`))) }
}, 1000) })
.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}`)))
} }
componentWillUnmount() { componentWillUnmount() {

1
store/store.go

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

4
util/config.go

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

Loading…
Cancel
Save