Rename zone in domain

This commit is contained in:
nemunaire 2020-04-22 10:17:16 +02:00
parent 898e5ed5c8
commit fa2ed5133f
19 changed files with 613 additions and 634 deletions

209
api/domains.go Normal file
View File

@ -0,0 +1,209 @@
package api
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"github.com/julienschmidt/httprouter"
"github.com/miekg/dns"
"git.happydns.org/happydns/config"
"git.happydns.org/happydns/model"
"git.happydns.org/happydns/storage"
)
func init() {
router.GET("/api/domains", apiAuthHandler(getDomains))
router.POST("/api/domains", apiAuthHandler(addDomain))
router.DELETE("/api/domains/:domain", apiAuthHandler(domainHandler(delDomain)))
router.GET("/api/domains/:domain", apiAuthHandler(domainHandler(getDomain)))
router.GET("/api/domains/:domain/rr", apiAuthHandler(domainHandler(axfrDomain)))
router.POST("/api/domains/:domain/rr", apiAuthHandler(domainHandler(addRR)))
router.DELETE("/api/domains/:domain/rr", apiAuthHandler(domainHandler(delRR)))
}
func getDomains(_ *config.Options, u *happydns.User, p httprouter.Params, body io.Reader) Response {
if domains, err := storage.MainStore.GetDomains(u); err != nil {
return APIErrorResponse{
err: err,
}
} else {
return APIResponse{
response: domains,
}
}
}
func addDomain(_ *config.Options, u *happydns.User, p httprouter.Params, body io.Reader) Response {
var uz happydns.Domain
err := json.NewDecoder(body).Decode(&uz)
if err != nil {
return APIErrorResponse{
err: err,
}
}
if len(uz.DomainName) <= 2 {
return APIErrorResponse{
err: errors.New("The given domain is invalid."),
}
}
if uz.DomainName[len(uz.DomainName)-1] != '.' {
uz.DomainName = uz.DomainName + "."
}
if storage.MainStore.DomainExists(uz.DomainName) {
return APIErrorResponse{
err: errors.New("This domain already exists."),
}
} else if err := storage.MainStore.CreateDomain(u, &uz); err != nil {
return APIErrorResponse{
err: err,
}
} else {
return APIResponse{
response: uz,
}
}
}
func delDomain(_ *config.Options, domain *happydns.Domain, body io.Reader) Response {
if err := storage.MainStore.DeleteDomain(domain); err != nil {
return APIErrorResponse{
err: err,
}
} else {
return APIResponse{
response: true,
}
}
}
func domainHandler(f func(*config.Options, *happydns.Domain, io.Reader) Response) func(*config.Options, *happydns.User, httprouter.Params, io.Reader) Response {
return func(opts *config.Options, u *happydns.User, ps httprouter.Params, body io.Reader) Response {
if domain, err := storage.MainStore.GetDomainByDN(u, ps.ByName("domain")); err != nil {
return APIErrorResponse{
status: http.StatusNotFound,
err: errors.New("Domain not found"),
}
} else {
return f(opts, domain, body)
}
}
}
func getDomain(_ *config.Options, domain *happydns.Domain, body io.Reader) Response {
return APIResponse{
response: domain,
}
}
func axfrDomain(opts *config.Options, domain *happydns.Domain, body io.Reader) Response {
source, err := storage.MainStore.GetSource(&happydns.User{Id: domain.IdUser}, domain.IdSource)
if err != nil {
return APIErrorResponse{
err: err,
}
}
rrs, err := source.ImportZone(domain)
if err != nil {
return APIErrorResponse{
err: err,
}
}
var ret []map[string]interface{}
for _, rr := range rrs {
ret = append(ret, map[string]interface{}{
"string": rr.String(),
"fields": rr,
})
}
return APIResponse{
response: ret,
}
}
type uploadedRR struct {
RR string `json:"string"`
}
func addRR(opts *config.Options, domain *happydns.Domain, body io.Reader) Response {
var urr uploadedRR
err := json.NewDecoder(body).Decode(&urr)
if err != nil {
return APIErrorResponse{
err: err,
}
}
rr, err := dns.NewRR(fmt.Sprintf("$ORIGIN %s\n$TTL %d\n%s", domain.DomainName, 3600, urr.RR))
if err != nil {
return APIErrorResponse{
err: err,
}
}
source, err := storage.MainStore.GetSource(&happydns.User{Id: domain.IdUser}, domain.IdSource)
if err != nil {
return APIErrorResponse{
err: err,
}
}
err = source.AddRR(domain, rr)
if err != nil {
return APIErrorResponse{
status: http.StatusInternalServerError,
err: err,
}
}
return APIResponse{
response: map[string]interface{}{
"string": rr.String(),
},
}
}
func delRR(opts *config.Options, domain *happydns.Domain, body io.Reader) Response {
var urr uploadedRR
err := json.NewDecoder(body).Decode(&urr)
if err != nil {
return APIErrorResponse{
err: err,
}
}
rr, err := dns.NewRR(urr.RR)
if err != nil {
return APIErrorResponse{
err: err,
}
}
source, err := storage.MainStore.GetSource(&happydns.User{Id: domain.IdUser}, domain.IdSource)
if err != nil {
return APIErrorResponse{
err: err,
}
}
err = source.DeleteRR(domain, rr)
if err != nil {
return APIErrorResponse{
status: http.StatusInternalServerError,
err: err,
}
}
return APIResponse{
response: true,
}
}

View File

@ -1,264 +0,0 @@
package api
import (
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"strings"
"time"
"github.com/julienschmidt/httprouter"
"github.com/miekg/dns"
"git.happydns.org/happydns/config"
"git.happydns.org/happydns/model"
"git.happydns.org/happydns/storage"
)
func init() {
router.GET("/api/zones", apiAuthHandler(getZones))
router.POST("/api/zones", apiAuthHandler(addZone))
router.DELETE("/api/zones/:zone", apiAuthHandler(zoneHandler(delZone)))
router.GET("/api/zones/:zone", apiAuthHandler(zoneHandler(getZone)))
router.GET("/api/zones/:zone/rr", apiAuthHandler(zoneHandler(axfrZone)))
router.POST("/api/zones/:zone/rr", apiAuthHandler(zoneHandler(addRR)))
router.DELETE("/api/zones/:zone/rr", apiAuthHandler(zoneHandler(delRR)))
}
func getZones(_ *config.Options, u *happydns.User, p httprouter.Params, body io.Reader) Response {
if zones, err := storage.MainStore.GetZones(u); err != nil {
return APIErrorResponse{
err: err,
}
} else {
return APIResponse{
response: zones,
}
}
}
func addZone(_ *config.Options, u *happydns.User, p httprouter.Params, body io.Reader) Response {
var uz happydns.Zone
err := json.NewDecoder(body).Decode(&uz)
if err != nil {
return APIErrorResponse{
err: err,
}
}
if len(uz.DomainName) <= 2 {
return APIErrorResponse{
err: errors.New("The given zone is invalid."),
}
}
if uz.DomainName[len(uz.DomainName)-1] != '.' {
uz.DomainName = uz.DomainName + "."
}
if len(uz.KeyName) > 1 && uz.KeyName[len(uz.KeyName)-1] != '.' {
uz.KeyName = uz.KeyName + "."
}
if storage.MainStore.ZoneExists(uz.DomainName) {
return APIErrorResponse{
err: errors.New("This zone already exists."),
}
} else if err := storage.MainStore.CreateZone(u, &uz); err != nil {
return APIErrorResponse{
err: err,
}
} else {
return APIResponse{
response: uz,
}
}
}
func delZone(_ *config.Options, zone *happydns.Zone, body io.Reader) Response {
if err := storage.MainStore.DeleteZone(zone); err != nil {
return APIErrorResponse{
err: err,
}
} else {
return APIResponse{
response: true,
}
}
}
func zoneHandler(f func(*config.Options, *happydns.Zone, io.Reader) Response) func(*config.Options, *happydns.User, httprouter.Params, io.Reader) Response {
return func(opts *config.Options, u *happydns.User, ps httprouter.Params, body io.Reader) Response {
if zone, err := storage.MainStore.GetZoneByDN(u, ps.ByName("zone")); err != nil {
return APIErrorResponse{
status: http.StatusNotFound,
err: errors.New("Domain not found"),
}
} else {
return f(opts, zone, body)
}
}
}
func getZone(_ *config.Options, zone *happydns.Zone, body io.Reader) Response {
return APIResponse{
response: zone,
}
}
func normalizeNSServer(opts *config.Options, srv string) string {
if srv == "" {
return opts.DefaultNameServer
} else if strings.Index(srv, ":") > -1 {
return srv
} else {
return srv + ":53"
}
}
func axfrZone(opts *config.Options, zone *happydns.Zone, body io.Reader) Response {
d := net.Dialer{}
con, err := d.Dial("tcp", normalizeNSServer(opts, zone.Server))
if err != nil {
return APIErrorResponse{
status: http.StatusInternalServerError,
err: err,
}
}
defer con.Close()
m := new(dns.Msg)
m.SetEdns0(4096, true)
m.SetAxfr(zone.DomainName)
m.SetTsig(zone.KeyName, zone.KeyAlgo, 300, time.Now().Unix())
dnscon := &dns.Conn{Conn: con}
transfer := &dns.Transfer{Conn: dnscon, TsigSecret: map[string]string{zone.KeyName: zone.Base64KeyBlob()}}
c, err := transfer.In(m, normalizeNSServer(opts, zone.Server))
if err != nil {
return APIErrorResponse{
status: http.StatusInternalServerError,
err: err,
}
}
var rrs []map[string]interface{}
for {
response, ok := <-c
if !ok {
break
}
for _, rr := range response.RR {
rrs = append(rrs, map[string]interface{}{
"string": rr.String(),
"fields": rr,
})
}
}
if len(rrs) > 0 {
rrs = rrs[0 : len(rrs)-1]
}
return APIResponse{
response: rrs,
}
}
type uploadedRR struct {
RR string `json:"string"`
}
func addRR(opts *config.Options, zone *happydns.Zone, body io.Reader) Response {
var urr uploadedRR
err := json.NewDecoder(body).Decode(&urr)
if err != nil {
return APIErrorResponse{
err: err,
}
}
rr, err := dns.NewRR(fmt.Sprintf("$ORIGIN %s\n$TTL %d\n%s", zone.DomainName, 3600, urr.RR))
if err != nil {
return APIErrorResponse{
err: err,
}
}
m := new(dns.Msg)
m.Id = dns.Id()
m.Opcode = dns.OpcodeUpdate
m.Question = make([]dns.Question, 1)
m.Question[0] = dns.Question{zone.DomainName, dns.TypeSOA, dns.ClassINET}
m.Insert([]dns.RR{rr})
c := new(dns.Client)
c.TsigSecret = map[string]string{zone.KeyName: zone.Base64KeyBlob()}
m.SetTsig(zone.KeyName, zone.KeyAlgo, 300, time.Now().Unix())
in, rtt, err := c.Exchange(m, normalizeNSServer(opts, zone.Server))
if err != nil {
return APIErrorResponse{
status: http.StatusInternalServerError,
err: err,
}
}
return APIResponse{
response: map[string]interface{}{
"in": *in,
"rtt": rtt,
"string": rr.String(),
},
}
}
func delRR(opts *config.Options, zone *happydns.Zone, body io.Reader) Response {
var urr uploadedRR
err := json.NewDecoder(body).Decode(&urr)
if err != nil {
return APIErrorResponse{
err: err,
}
}
rr, err := dns.NewRR(urr.RR)
if err != nil {
return APIErrorResponse{
err: err,
}
}
m := new(dns.Msg)
m.Id = dns.Id()
m.Opcode = dns.OpcodeUpdate
m.Question = make([]dns.Question, 1)
m.Question[0] = dns.Question{zone.DomainName, dns.TypeSOA, dns.ClassINET}
m.Remove([]dns.RR{rr})
c := new(dns.Client)
c.TsigSecret = map[string]string{zone.KeyName: zone.Base64KeyBlob()}
m.SetTsig(zone.KeyName, zone.KeyAlgo, 300, time.Now().Unix())
in, rtt, err := c.Exchange(m, normalizeNSServer(opts, zone.Server))
if err != nil {
return APIErrorResponse{
status: http.StatusInternalServerError,
err: err,
}
}
return APIResponse{
response: map[string]interface{}{
"in": *in,
"rtt": rtt,
},
}
}

View File

@ -26,39 +26,39 @@ const routes = [
}
},
{
path: '/zones',
name: 'zones',
path: '/domains',
name: 'domains',
component: function () {
return import(/* webpackChunkName: "zone-list" */ '../views/zone-list.vue')
return import(/* webpackChunkName: "domain-list" */ '../views/domain-list.vue')
}
},
{
path: '/zones/:zone',
path: '/domains/:zone',
component: function () {
return import(/* webpackChunkName: "zone" */ '../views/zone.vue')
return import(/* webpackChunkName: "domain" */ '../views/domain.vue')
},
children: [
{
path: '',
name: 'zone',
name: 'domain-source',
component: function () {
return import(/* webpackChunkName: "zone" */ '../views/zone-details.vue')
return import(/* webpackChunkName: "domain-source" */ '../views/domain-source.vue')
}
},
{
path: 'services',
name: 'zone-services',
component: function () {
return import(/* webpackChunkName: "zone" */ '../views/zone-services.vue')
return import(/* webpackChunkName: "zone-services" */ '../views/zone-services.vue')
}
}
]
},
{
path: '/zones/:zone/records',
path: '/zones/:domain/records',
name: 'zone-records',
component: function () {
return import(/* webpackChunkName: "zone" */ '../views/zone-records.vue')
return import(/* webpackChunkName: "zone-records" */ '../views/zone-records.vue')
}
}
]

View File

@ -4,7 +4,7 @@
<script>
import Home from '@/views/Home'
import ZoneList from '@/views/zone-list'
import ZoneList from '@/views/domain-list'
export default {
data () {

View File

@ -5,10 +5,10 @@
<div class="offset-md-2 col-md-8">
<b-list-group>
<b-list-group-item v-if="loading" class="text-center">
<b-spinner variant="secondary" label="Spinning"></b-spinner> Retrieving your zones...
<b-spinner variant="secondary" label="Spinning"></b-spinner> Retrieving your domains...
</b-list-group-item>
<b-list-group-item :to="'zones/' + zone.domain" v-for="(zone, index) in zones" v-bind:key="index" class="d-flex justify-content-between align-items-center">
{{ zone.domain }}
<b-list-group-item :to="'domains/' + domain.domain" v-for="(domain, index) in domains" v-bind:key="index" class="d-flex justify-content-between align-items-center">
{{ domain.domain }}
<b-badge variant="success">OK</b-badge>
</b-list-group-item>
</b-list-group>
@ -19,9 +19,9 @@
<b-input-group-prepend>
<b-icon icon="plus"></b-icon>
</b-input-group-prepend>
<input placeholder="my.new.zone" v-model="newForm.domain" style="border:none; flex: 1 1 auto;">
<input placeholder="my.new.domain" v-model="newForm.domain" style="border:none; flex: 1 1 auto;">
<b-input-group-append v-show="newForm.domain.length">
<b-button type="submit" variant="outline-primary">Add new zone</b-button>
<b-button type="submit" variant="outline-primary">Add new domain</b-button>
</b-input-group-append>
</b-input-group>
</b-list-group-item>
@ -31,9 +31,9 @@
</b-row>
<b-modal
id="newZoneModal"
id="newDomainModal"
ref="modal"
title="Attach new zone"
title="Attach new domain"
@show="resetModal"
@shown="modalShown"
@ok="handleOk"
@ -115,25 +115,25 @@ export default {
domainNameState: null,
loading: true,
newForm: { domain: '', storage_facility: 'live' },
zones: []
domains: []
}
},
mounted () {
setTimeout(() =>
axios
.get('/api/zones')
.then(response => { this.zones = response.data; this.loading = false; return true })
.get('/api/domains')
.then(response => { this.domains = response.data; this.loading = false; return true })
, 100)
},
methods: {
show (zone) {
this.$router.push('/zones/' + zone.domain)
show (domain) {
this.$router.push('/domains/' + domain.domain)
},
showModal () {
this.$bvModal.show('newZoneModal')
this.$bvModal.show('newDomainModal')
},
modalShown () {
@ -162,9 +162,9 @@ export default {
this.handleSubmit()
},
attachZone () {
attachDomain () {
axios
.post('/api/zones', {
.post('/api/domains', {
domain: this.newForm.domain,
server: this.newForm.server,
keyname: this.newForm.keyname,
@ -173,11 +173,11 @@ export default {
})
.then(
(response) => {
if (this.zones == null) this.zones = []
this.zones.push(response.data)
if (this.domains == null) this.domains = []
this.domains.push(response.data)
},
(error) => {
alert('Unable to attach the given zone: ' + error.response.data.errmsg)
alert('Unable to attach the given domain: ' + error.response.data.errmsg)
}
)
},
@ -187,7 +187,7 @@ export default {
return
}
this.attachZone()
this.attachDomain()
this.$nextTick(() => {
this.$refs.modal.hide()

View File

@ -0,0 +1,69 @@
<template>
<b-container class="mt-2">
<b-row>
<b-col cols="3" class="text-right" style="background-color: #EAFFEC">
<router-link to="/domains/" class="btn font-weight-bolder"><b-icon icon="chevron-up"></b-icon></router-link>
</b-col>
<b-col cols="9">
<h2 class="mt-2 mb-3">
{{ domain.domain }}
</h2>
</b-col>
</b-row>
<b-alert variant="danger" :show="error.length != 0"><strong>Error:</strong> {{ error }}</b-alert>
<div class="text-center" v-if="!domain && error.length == 0">
<b-spinner label="Spinning"></b-spinner>
<p>Loading the domain&nbsp;&hellip;</p>
</div>
<b-row>
<b-col cols="3" style="background-color: #EAFFEC">
<b-navbar class="flex-column">
<b-nav pills vertical>
<b-nav-item :to="'/domains/' + domain.domain" :active="$route.name == 'domain-source'">Domain source</b-nav-item>
<b-nav-item :to="'/domains/' + domain.domain + '/services'" :active="$route.name == 'zone-services'">View services</b-nav-item>
<b-nav-item :to="'/zones/' + domain.domain + '/records'" :active="$route.name == 'zone-records'">View records</b-nav-item>
<b-nav-item :to="'/domain/' + domain.domain + '/monitoring'" :active="$route.name == 'domain-monitoring'">Monitoring</b-nav-item>
<hr>
<b-nav-form>
<b-button type="button" @click="detachDomain()" variant="outline-danger"><b-icon icon="trash-fill"></b-icon> Stop managing this domain</b-button>
</b-nav-form>
</b-nav>
</b-navbar>
</b-col>
<b-col cols="9">
<router-view :domain="domain"></router-view>
</b-col>
</b-row>
</b-container>
</template>
<script>
import axios from 'axios'
export default {
data: function () {
return {
error: '',
domain: {}
}
},
mounted () {
var mydomain = this.$route.params.domain
axios
.get('/api/domains/' + mydomain)
.then(response => (this.domain = response.data))
},
methods: {
detachDomain () {
axios
.delete('/api/domains/' + this.domain.domain)
.then(response => (
this.$router.push('/domains/')
))
}
}
}
</script>

View File

@ -5,8 +5,8 @@
<b-button-group v-show="rrs && rrs.length" class="float-right ml-2" size="sm" variant="secondary">
<b-button :pressed.sync="showDNSSEC">DNSSEC</b-button>
</b-button-group>
<router-link :to="'/zones/' + zone.domain" class="btn"><b-icon icon="chevron-left"></b-icon></router-link>
{{ zone.domain }}
<router-link :to="'/domains/' + domain.domain" class="btn"><b-icon icon="chevron-left"></b-icon></router-link>
{{ domain.domain }}
<small class="text-muted">Resource Records <span v-if="rrs && rrs.length">({{ rrsFiltered.length }}/{{ rrs.length }})</span></small>
</h2>
<b-alert variant="danger" :show="error.length != 0"><strong>Error:</strong> {{ error }}</b-alert>
@ -74,7 +74,7 @@ export default {
expandrrs: {},
rrs: [],
query: '',
zone: {}
domain: {}
}
},
@ -94,13 +94,13 @@ export default {
},
mounted () {
var myzone = this.$route.params.zone
var mydomain = this.$route.params.domain
axios
.get('/api/zones/' + myzone)
.then(response => (this.zone = response.data))
.get('/api/domains/' + mydomain)
.then(response => (this.domain = response.data))
axios
.get('/api/zones/' + myzone + '/rr')
.get('/api/domains/' + mydomain + '/rr')
.then(
(response) => (this.rrs = response.data),
(error) => (this.error = error.response.data.errmsg)
@ -119,24 +119,24 @@ export default {
newRR (idx) {
axios
.post('/api/zones/' + this.$route.params.zone + '/rr', {
.post('/api/domains/' + this.$route.params.domain + '/rr', {
string: this.rrsFiltered[idx].string
})
.then(
(response) => {
axios
.get('/api/zones/' + this.$route.params.zone + '/rr')
.get('/api/domains/' + this.$route.params.domain + '/rr')
.then(response => (this.rrs = response.data))
},
(error) => {
alert('An error occurs when trying to add RR to zone: ' + error.response.data.errmsg)
alert('An error occurs when trying to add RR to the zone: ' + error.response.data.errmsg)
}
)
},
deleteRR (idx) {
axios
.delete('/api/zones/' + this.$route.params.zone + '/rr', {
.delete('/api/domains/' + this.$route.params.domain + '/rr', {
data: {
string: this.rrsFiltered[idx].string
}
@ -144,11 +144,11 @@ export default {
.then(
(response) => {
axios
.get('/api/zones/' + this.$route.params.zone + '/rr')
.get('/api/domains/' + this.$route.params.domain + '/rr')
.then(response => (this.rrs = response.data))
},
(error) => {
alert('An error occurs when trying to delete RR in zone: ' + error.response.data.errmsg)
alert('An error occurs when trying to delete RR in the zone: ' + error.response.data.errmsg)
}
)
}

View File

@ -1,74 +0,0 @@
<template>
<b-container class="mt-2">
<b-row>
<b-col cols="3" class="text-right" style="background-color: #EAFFEC">
<router-link to="/zones/" class="btn font-weight-bolder"><b-icon icon="chevron-up"></b-icon></router-link>
</b-col>
<b-col cols="9">
<h2 class="mt-2 mb-3">
{{ zone.domain }}
</h2>
</b-col>
</b-row>
<b-alert variant="danger" :show="error.length != 0"><strong>Error:</strong> {{ error }}</b-alert>
<div class="text-center" v-if="!zone && error.length == 0">
<b-spinner label="Spinning"></b-spinner>
<p>Loading the zone&nbsp;&hellip;</p>
</div>
<b-row>
<b-col cols="3" style="background-color: #EAFFEC">
<b-navbar class="flex-column">
<b-nav pills vertical>
<b-nav-item :to="'/zones/' + zone.domain" :active="$route.name == 'zone'">My zone</b-nav-item>
<b-nav-item :to="'/zones/' + zone.domain + '/services'" :active="$route.name == 'zone-services'">View services</b-nav-item>
<b-nav-item :to="'/zones/' + zone.domain + '/records'" :active="$route.name == 'zone-records'">View records</b-nav-item>
<b-nav-item :to="'/zones/' + zone.domain + '/monitoring'" :active="$route.name == 'zone-monitoring'">Monitoring</b-nav-item>
<hr>
<b-nav-form>
<b-button type="button" @click="deleteZone(zone)" variant="outline-danger"><b-icon icon="trash-fill"></b-icon> Delete my zone</b-button>
</b-nav-form>
</b-nav>
</b-navbar>
</b-col>
<b-col cols="9">
<router-view></router-view>
</b-col>
</b-row>
</b-container>
</template>
<script>
import axios from 'axios'
export default {
data: function () {
return {
error: '',
zone: {}
}
},
mounted () {
var myzone = this.$route.params.zone
axios
.get('/api/zones/' + myzone)
.then(response => (this.zone = response.data))
},
methods: {
deleteZone (zone) {
axios
.delete('/api/zones/' + zone.domain)
.then(response => (
axios
.get('/api/zones')
.then(response => {
this.zones = response.data
this.$router.go('/zones/')
})
))
}
}
}
</script>

34
model/domain.go Normal file
View File

@ -0,0 +1,34 @@
package happydns
import (
"strings"
)
type Domain struct {
Id int64 `json:"id"`
IdUser int64
IdSource int64
DomainName string `json:"domain"`
}
type Domains []*Domain
func (d *Domain) NormalizedNSServer() string {
if strings.Index(d.DomainName, ":") > -1 {
return d.DomainName
} else {
return d.DomainName + ":53"
}
}
func NewDomain(u *User, s Source, dn string) (d *Domain) {
d = &Domain{
IdUser: u.Id,
//IdSource: s.GetId(),
DomainName: dn,
}
d.DomainName = d.NormalizedNSServer()
return
}

View File

@ -1,34 +0,0 @@
package happydns
import (
"encoding/base64"
)
type Zone struct {
Id int64 `json:"id"`
IdUser int64
DomainName string `json:"domain"`
Server string `json:"server,omitempty"`
KeyName string `json:"keyname,omitempty"`
KeyAlgo string `json:"algorithm,omitempty"`
KeyBlob []byte `json:"keyblob,omitempty"`
StorageFacility string `json:"storage_facility,omitempty"`
}
type Zones []*Zone
func NewZone(u User, dn, server, keyname, algo string, keyblob []byte, storage string) *Zone {
return &Zone{
IdUser: u.Id,
DomainName: dn,
Server: server,
KeyName: keyname,
KeyAlgo: algo,
KeyBlob: keyblob,
StorageFacility: storage,
}
}
func (z *Zone) Base64KeyBlob() string {
return base64.StdEncoding.EncodeToString(z.KeyBlob)
}

View File

@ -33,6 +33,20 @@ func init() {
fwd_request(w, r, opts.DevProxy)
}
})
api.Router().GET("/domains/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
opts := r.Context().Value("opts").(*config.Options)
if opts.DevProxy == "" {
if data, err := Asset("htdocs/dist/index.html"); err != nil {
fmt.Fprintf(w, "{\"errmsg\":%q}", err)
} else {
w.Write(data)
}
} else {
r.URL.Path = "/"
fwd_request(w, r, opts.DevProxy)
}
})
api.Router().GET("/join", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
opts := r.Context().Value("opts").(*config.Options)

View File

@ -8,6 +8,16 @@ type Storage interface {
DoMigration() error
Close() error
GetDomains(u *happydns.User) (happydns.Domains, error)
GetDomain(u *happydns.User, id int) (*happydns.Domain, error)
GetDomainByDN(u *happydns.User, dn string) (*happydns.Domain, error)
DomainExists(dn string) bool
CreateDomain(u *happydns.User, z *happydns.Domain) error
UpdateDomain(z *happydns.Domain) error
UpdateDomainOwner(z *happydns.Domain, newOwner *happydns.User) error
DeleteDomain(z *happydns.Domain) error
ClearDomains() error
GetSession(id []byte) (*happydns.Session, error)
CreateSession(session *happydns.Session) error
UpdateSession(session *happydns.Session) error
@ -22,14 +32,4 @@ type Storage interface {
UpdateUser(user *happydns.User) error
DeleteUser(user *happydns.User) error
ClearUsers() error
GetZones(u *happydns.User) (happydns.Zones, error)
GetZone(u *happydns.User, id int) (*happydns.Zone, error)
GetZoneByDN(u *happydns.User, dn string) (*happydns.Zone, error)
ZoneExists(dn string) bool
CreateZone(u *happydns.User, z *happydns.Zone) error
UpdateZone(z *happydns.Zone) error
UpdateZoneOwner(z *happydns.Zone, newOwner *happydns.User) error
DeleteZone(z *happydns.Zone) error
ClearZones() error
}

127
storage/leveldb/domain.go Normal file
View File

@ -0,0 +1,127 @@
package database
import (
"fmt"
"git.happydns.org/happydns/model"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
)
func (s *LevelDBStorage) GetDomains(u *happydns.User) (domains happydns.Domains, err error) {
iter := s.search("domain-")
defer iter.Release()
for iter.Next() {
var z happydns.Domain
err = decodeData(iter.Value(), &z)
if err != nil {
return
}
if z.IdUser == u.Id {
domains = append(domains, &z)
}
}
return
}
func (s *LevelDBStorage) GetDomain(u *happydns.User, id int) (z *happydns.Domain, err error) {
z = &happydns.Domain{}
err = s.get(fmt.Sprintf("domain-%d", id), &z)
if z.IdUser != u.Id {
z = nil
err = leveldb.ErrNotFound
}
return
}
func (s *LevelDBStorage) GetDomainByDN(u *happydns.User, dn string) (*happydns.Domain, error) {
domains, err := s.GetDomains(u)
if err != nil {
return nil, err
}
for _, domain := range domains {
if domain.DomainName == dn {
return domain, nil
}
}
return nil, leveldb.ErrNotFound
}
func (s *LevelDBStorage) DomainExists(dn string) bool {
iter := s.search("domain-")
defer iter.Release()
for iter.Next() {
var z happydns.Domain
err := decodeData(iter.Value(), &z)
if err != nil {
continue
}
if z.DomainName == dn {
return true
}
}
return false
}
func (s *LevelDBStorage) CreateDomain(u *happydns.User, z *happydns.Domain) error {
key, id, err := s.findInt63Key("domain-")
if err != nil {
return err
}
z.Id = id
z.IdUser = u.Id
return s.put(key, z)
}
func (s *LevelDBStorage) UpdateDomain(z *happydns.Domain) error {
return s.put(fmt.Sprintf("domain-%d", z.Id), z)
}
func (s *LevelDBStorage) UpdateDomainOwner(z *happydns.Domain, newOwner *happydns.User) error {
z.IdUser = newOwner.Id
return s.put(fmt.Sprintf("domain-%d", z.Id), z)
}
func (s *LevelDBStorage) DeleteDomain(z *happydns.Domain) error {
return s.delete(fmt.Sprintf("domain-%d", z.Id))
}
func (s *LevelDBStorage) ClearDomains() error {
tx, err := s.db.OpenTransaction()
if err != nil {
return err
}
iter := tx.NewIterator(util.BytesPrefix([]byte("domain-")), nil)
defer iter.Release()
for iter.Next() {
err = tx.Delete(iter.Key(), nil)
if err != nil {
tx.Discard()
return err
}
}
err = tx.Commit()
if err != nil {
tx.Discard()
return err
}
return nil
}

View File

@ -1,127 +0,0 @@
package database
import (
"fmt"
"git.happydns.org/happydns/model"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
)
func (s *LevelDBStorage) GetZones(u *happydns.User) (zones happydns.Zones, err error) {
iter := s.search("zone-")
defer iter.Release()
for iter.Next() {
var z happydns.Zone
err = decodeData(iter.Value(), &z)
if err != nil {
return
}
if z.IdUser == u.Id {
zones = append(zones, &z)
}
}
return
}
func (s *LevelDBStorage) GetZone(u *happydns.User, id int) (z *happydns.Zone, err error) {
z = &happydns.Zone{}
err = s.get(fmt.Sprintf("zone-%d", id), &z)
if z.IdUser != u.Id {
z = nil
err = leveldb.ErrNotFound
}
return
}
func (s *LevelDBStorage) GetZoneByDN(u *happydns.User, dn string) (*happydns.Zone, error) {
zones, err := s.GetZones(u)
if err != nil {
return nil, err
}
for _, zone := range zones {
if zone.DomainName == dn {
return zone, nil
}
}
return nil, leveldb.ErrNotFound
}
func (s *LevelDBStorage) ZoneExists(dn string) bool {
iter := s.search("zone-")
defer iter.Release()
for iter.Next() {
var z happydns.Zone
err := decodeData(iter.Value(), &z)
if err != nil {
continue
}
if z.DomainName == dn {
return true
}
}
return false
}
func (s *LevelDBStorage) CreateZone(u *happydns.User, z *happydns.Zone) error {
key, id, err := s.findInt63Key("zone-")
if err != nil {
return err
}
z.Id = id
z.IdUser = u.Id
return s.put(key, z)
}
func (s *LevelDBStorage) UpdateZone(z *happydns.Zone) error {
return s.put(fmt.Sprintf("zone-%d", z.Id), z)
}
func (s *LevelDBStorage) UpdateZoneOwner(z *happydns.Zone, newOwner *happydns.User) error {
z.IdUser = newOwner.Id
return s.put(fmt.Sprintf("zone-%d", z.Id), z)
}
func (s *LevelDBStorage) DeleteZone(z *happydns.Zone) error {
return s.delete(fmt.Sprintf("zone-%d", z.Id))
}
func (s *LevelDBStorage) ClearZones() error {
tx, err := s.db.OpenTransaction()
if err != nil {
return err
}
iter := tx.NewIterator(util.BytesPrefix([]byte("zone-")), nil)
defer iter.Release()
for iter.Next() {
err = tx.Delete(iter.Key(), nil)
if err != nil {
tx.Discard()
return err
}
}
err = tx.Commit()
if err != nil {
tx.Discard()
return err
}
return nil
}

81
storage/mysql/domain.go Normal file
View File

@ -0,0 +1,81 @@
package database
import (
"git.happydns.org/happydns/model"
)
func (s *MySQLStorage) GetDomains(u *happydns.User) (domains happydns.Domains, err error) {
if rows, errr := s.db.Query("SELECT id_domain, id_user, id_source, domain FROM domains WHERE id_user = ?", u.Id); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var z happydns.Domain
if err = rows.Scan(&z.Id, &z.IdUser, &z.IdSource, &z.DomainName); err != nil {
return
}
domains = append(domains, &z)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func (s *MySQLStorage) GetDomain(u *happydns.User, id int) (z *happydns.Domain, err error) {
z = &happydns.Domain{}
err = s.db.QueryRow("SELECT id_domain, id_user, id_source, domain FROM domains WHERE id_domain=? AND id_user=?", id, u.Id).Scan(&z.Id, &z.IdUser, &z.IdSource, &z.DomainName)
return
}
func (s *MySQLStorage) GetDomainByDN(u *happydns.User, dn string) (z *happydns.Domain, err error) {
z = &happydns.Domain{}
err = s.db.QueryRow("SELECT id_domain, id_user, id_source, domain FROM domains WHERE domain=? AND id_user=?", dn, u.Id).Scan(&z.Id, &z.IdUser, &z.IdSource, &z.DomainName)
return
}
func (s *MySQLStorage) DomainExists(dn string) bool {
var z int
err := s.db.QueryRow("SELECT 1 FROM domains WHERE domain=?", dn).Scan(&z)
return err == nil && z == 1
}
func (s *MySQLStorage) CreateDomain(u *happydns.User, src happydns.SourceType, z *happydns.Domain) error {
if res, err := s.db.Exec("INSERT INTO domains (id_user, id_source, domain) VALUES (?, ?, ?)", u.Id, src.Id, z.DomainName); err != nil {
return err
} else if z.Id, err = res.LastInsertId(); err != nil {
return err
} else {
return nil
}
}
func (s *MySQLStorage) UpdateDomain(z *happydns.Domain) error {
if _, err := s.db.Exec("UPDATE domains SET id_source = ?, domain = ? WHERE id_domain = ?", z.IdSource, z.DomainName, z.Id); err != nil {
return err
} else {
return nil
}
}
func (s *MySQLStorage) UpdateDomainOwner(z *happydns.Domain, newOwner *happydns.User) error {
if _, err := s.db.Exec("UPDATE domains SET id_user = ? WHERE id_domain = ?", newOwner.Id, z.Id); err != nil {
return err
} else {
z.IdUser = newOwner.Id
return nil
}
}
func (s *MySQLStorage) DeleteDomain(z *happydns.Domain) error {
_, err := s.db.Exec("DELETE FROM domains WHERE id_domain = ?", z.Id)
return err
}
func (s *MySQLStorage) ClearDomains() error {
_, err := s.db.Exec("DELETE FROM domains")
return err
}

View File

@ -2,7 +2,7 @@
package database // import "happydns.org/database"
const schemaVersion = 2
const schemaVersion = 3
var schemaRevisions = map[uint16]string{
1: `CREATE TABLE schema_version (
@ -50,5 +50,18 @@ ALTER TABLE user_sessions
ALTER TABLE zones
CHANGE id_user id_user BIGINT NOT NULL;
`,
3: `ALTER TABLE zones
DROP COLUMN server;
ALTER TABLE zones
DROP COLUMN key_name;
ALTER TABLE zones
DROP COLUMN key_algo;
ALTER TABLE zones
DROP COLUMN key_blob;
ALTER TABLE zones
DROP COLUMN storage_facility;
RENAME TABLE zones TO domains;
`,
}

View File

@ -0,0 +1,12 @@
ALTER TABLE zones
DROP COLUMN server;
ALTER TABLE zones
DROP COLUMN key_name;
ALTER TABLE zones
DROP COLUMN key_algo;
ALTER TABLE zones
DROP COLUMN key_blob;
ALTER TABLE zones
DROP COLUMN storage_facility;
RENAME TABLE zones TO domains;

View File

@ -1,81 +0,0 @@
package database
import (
"git.happydns.org/happydns/model"
)
func (s *MySQLStorage) GetZones(u *happydns.User) (zones happydns.Zones, err error) {
if rows, errr := s.db.Query("SELECT id_zone, id_user, domain, server, key_name, key_algo, key_blob, storage_facility FROM zones WHERE id_user = ?", u.Id); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var z happydns.Zone
if err = rows.Scan(&z.Id, &z.IdUser, &z.DomainName, &z.Server, &z.KeyName, &z.KeyAlgo, &z.KeyBlob, &z.StorageFacility); err != nil {
return
}
zones = append(zones, &z)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func (s *MySQLStorage) GetZone(u *happydns.User, id int) (z *happydns.Zone, err error) {
z = &happydns.Zone{}
err = s.db.QueryRow("SELECT id_zone, id_user, domain, server, key_name, key_algo, key_blob, storage_facility FROM zones WHERE id_zone=? AND id_user=?", id, u.Id).Scan(&z.Id, &z.IdUser, &z.DomainName, &z.Server, &z.KeyName, &z.KeyAlgo, &z.KeyBlob, &z.StorageFacility)
return
}
func (s *MySQLStorage) GetZoneByDN(u *happydns.User, dn string) (z *happydns.Zone, err error) {
z = &happydns.Zone{}
err = s.db.QueryRow("SELECT id_zone, id_user, domain, server, key_name, key_algo, key_blob, storage_facility FROM zones WHERE domain=? AND id_user=?", dn, u.Id).Scan(&z.Id, &z.IdUser, &z.DomainName, &z.Server, &z.KeyName, &z.KeyAlgo, &z.KeyBlob, &z.StorageFacility)
return
}
func (s *MySQLStorage) ZoneExists(dn string) bool {
var z int
err := s.db.QueryRow("SELECT 1 FROM zones WHERE domain=?", dn).Scan(&z)
return err == nil && z == 1
}
func (s *MySQLStorage) CreateZone(u *happydns.User, z *happydns.Zone) error {
if res, err := s.db.Exec("INSERT INTO zones (id_user, domain, server, key_name, key_blob, storage_facility) VALUES (?, ?, ?, ?, ?, ?)", u.Id, z.DomainName, z.Server, z.KeyName, z.KeyBlob, z.StorageFacility); err != nil {
return err
} else if z.Id, err = res.LastInsertId(); err != nil {
return err
} else {
return nil
}
}
func (s *MySQLStorage) UpdateZone(z *happydns.Zone) error {
if _, err := s.db.Exec("UPDATE zones SET domain = ?, key_name = ?, key_algo = ?, key_blob = ?, storage_facility = ? WHERE id_zone = ?", z.DomainName, z.KeyName, z.KeyAlgo, z.KeyBlob, z.StorageFacility, z.Id); err != nil {
return err
} else {
return nil
}
}
func (s *MySQLStorage) UpdateZoneOwner(z *happydns.Zone, newOwner *happydns.User) error {
if _, err := s.db.Exec("UPDATE zones SET id_user = ? WHERE id_zone = ?", newOwner.Id, z.Id); err != nil {
return err
} else {
z.IdUser = newOwner.Id
return nil
}
}
func (s *MySQLStorage) DeleteZone(z *happydns.Zone) error {
_, err := s.db.Exec("DELETE FROM zones WHERE id_zone = ?", z.Id)
return err
}
func (s *MySQLStorage) ClearZones() error {
_, err := s.db.Exec("DELETE FROM zones")
return err
}