2023-12-24 10:18:08 +00:00
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2024 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
2020-05-04 14:58:02 +00:00
//
2023-12-24 10:18:08 +00:00
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
2020-05-04 14:58:02 +00:00
//
2023-12-24 10:18:08 +00:00
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
2020-05-04 14:58:02 +00:00
//
2023-12-24 10:18:08 +00:00
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
2020-05-04 14:58:02 +00:00
//
2023-12-24 10:18:08 +00:00
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
2020-05-04 14:58:02 +00:00
2020-04-22 08:17:16 +00:00
package api
import (
"fmt"
2021-05-05 01:48:16 +00:00
"log"
2020-04-22 08:17:16 +00:00
"net/http"
2023-11-24 03:18:36 +00:00
"sort"
2020-04-22 08:17:16 +00:00
2021-05-05 01:48:16 +00:00
"github.com/gin-gonic/gin"
2020-04-22 08:17:16 +00:00
"github.com/miekg/dns"
2023-09-07 09:37:18 +00:00
"git.happydns.org/happyDomain/config"
"git.happydns.org/happyDomain/model"
"git.happydns.org/happyDomain/storage"
2020-04-22 08:17:16 +00:00
)
2021-05-05 01:48:16 +00:00
func declareDomainsRoutes ( cfg * config . Options , router * gin . RouterGroup ) {
router . GET ( "/domains" , GetDomains )
router . POST ( "/domains" , addDomain )
2020-05-09 11:14:29 +00:00
2021-05-05 01:48:16 +00:00
apiDomainsRoutes := router . Group ( "/domains/:domain" )
apiDomainsRoutes . Use ( DomainHandler )
2020-05-09 11:14:29 +00:00
2021-05-05 01:48:16 +00:00
apiDomainsRoutes . GET ( "" , GetDomain )
2021-10-27 07:57:24 +00:00
apiDomainsRoutes . PUT ( "" , UpdateDomain )
2021-05-05 01:48:16 +00:00
apiDomainsRoutes . DELETE ( "" , delDomain )
2023-11-24 03:18:36 +00:00
apiDomainsRoutes . GET ( "/logs" , GetDomainLogs )
2021-05-05 01:48:16 +00:00
declareZonesRoutes ( cfg , apiDomainsRoutes )
2020-04-22 08:17:16 +00:00
}
2023-08-05 16:15:52 +00:00
// GetDomains retrieves all domains belonging to the user.
//
// @Summary Retrieve user's domains
// @Schemes
// @Description Retrieve all domains belonging to the user.
// @Tags domains
// @Accept json
// @Produce json
// @Security securitydefinitions.basic
// @Success 200 {array} happydns.Domain
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Unable to retrieve user's domains"
// @Router /domains [get]
2021-05-05 01:48:16 +00:00
func GetDomains ( c * gin . Context ) {
user := myUser ( c )
if user == nil {
c . AbortWithStatusJSON ( http . StatusUnauthorized , gin . H { "errmsg" : "User not defined" } )
return
}
if domains , err := storage . MainStore . GetDomains ( user ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s: An error occurs when trying to GetDomains: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusNotFound , gin . H { "errmsg" : err } )
2020-05-05 15:24:54 +00:00
} else if len ( domains ) > 0 {
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , domains )
2020-05-05 15:24:54 +00:00
} else {
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , [ ] happydns . Domain { } )
2020-04-22 08:17:16 +00:00
}
}
2023-08-05 16:15:52 +00:00
// addDomain appends a new domain to those managed.
//
// @Summary Manage a new domain
// @Schemes
// @Description Append a new domain to those managed.
// @Tags domains
// @Accept json
// @Produce json
// @Param body body happydns.DomainMinimal true "Domain object that you want to manage through happyDomain."
// @Security securitydefinitions.basic
// @Success 200 {object} happydns.Domain
// @Failure 400 {object} happydns.Error "Error in received data"
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 500 {object} happydns.Error "Unable to retrieve current user's domains"
// @Router /domains [post]
2021-05-05 01:48:16 +00:00
func addDomain ( c * gin . Context ) {
2020-04-22 08:17:16 +00:00
var uz happydns . Domain
2021-05-05 01:48:16 +00:00
err := c . ShouldBindJSON ( & uz )
2020-04-22 08:17:16 +00:00
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s sends invalid Domain JSON: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Something is wrong in received data: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-04-22 08:17:16 +00:00
}
if len ( uz . DomainName ) <= 2 {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : "The given domain is invalid." } )
return
2020-04-22 08:17:16 +00:00
}
2020-05-02 08:55:08 +00:00
uz . DomainName = dns . Fqdn ( uz . DomainName )
if _ , ok := dns . IsDomainName ( uz . DomainName ) ; ! ok {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "%q is not a valid domain name." , uz . DomainName ) } )
return
2020-05-02 08:55:08 +00:00
}
2021-05-05 01:48:16 +00:00
user := c . MustGet ( "LoggedUser" ) . ( * happydns . User )
2021-05-21 11:08:18 +00:00
provider , err := storage . MainStore . GetProvider ( user , uz . IdProvider )
2020-05-02 08:55:08 +00:00
if err != nil {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Unable to find the provider." ) } )
return
2020-04-22 08:17:16 +00:00
}
if storage . MainStore . DomainExists ( uz . DomainName ) {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : "This domain has already been imported." } )
return
2020-05-02 08:55:08 +00:00
2021-05-21 11:08:18 +00:00
} else if err := provider . DomainExists ( uz . DomainName ) ; err != nil {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : err . Error ( ) } )
return
} else if err := storage . MainStore . CreateDomain ( user , & uz ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s was unable to CreateDomain: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are unable to create your domain now." } )
return
2020-04-22 08:17:16 +00:00
} else {
2023-11-24 03:18:36 +00:00
storage . MainStore . CreateDomainLog ( & uz , happydns . NewDomainLog ( c . MustGet ( "LoggedUser" ) . ( * happydns . User ) , happydns . LOG_INFO , fmt . Sprintf ( "Domain name %s added." , uz . DomainName ) ) )
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , uz )
2020-04-22 08:17:16 +00:00
}
}
2021-05-05 01:48:16 +00:00
func DomainHandler ( c * gin . Context ) {
// Get a valid user
user := myUser ( c )
if user == nil {
c . AbortWithStatusJSON ( http . StatusUnauthorized , gin . H { "errmsg" : "User not defined." } )
return
2020-04-22 08:17:16 +00:00
}
2022-10-25 16:16:34 +00:00
dnid , err := happydns . NewIdentifierFromString ( c . Param ( "domain" ) )
if err != nil {
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Invalid domain identifier: %s" , err . Error ( ) ) } )
return
}
domain , err := storage . MainStore . GetDomain ( user , dnid )
2021-05-05 01:48:16 +00:00
if err != nil {
c . AbortWithStatusJSON ( http . StatusNotFound , gin . H { "errmsg" : "Domain not found" } )
return
}
2023-08-05 16:15:52 +00:00
// If provider is provided, check that the domain is a parent of the provider
var provider * happydns . ProviderMeta
if src , exists := c . Get ( "provider" ) ; exists {
provider = & src . ( * happydns . ProviderCombined ) . ProviderMeta
} else if src , exists := c . Get ( "providermeta" ) ; exists {
provider = src . ( * happydns . ProviderMeta )
2021-05-05 01:48:16 +00:00
}
2023-08-05 16:15:52 +00:00
if provider != nil && ! provider . Id . Equals ( domain . IdProvider ) {
c . AbortWithStatusJSON ( http . StatusNotFound , gin . H { "errmsg" : "Domain not found (not child of provider)" } )
2021-05-05 01:48:16 +00:00
return
2020-04-22 08:17:16 +00:00
}
2021-05-05 01:48:16 +00:00
c . Set ( "domain" , domain )
c . Next ( )
2020-04-22 08:17:16 +00:00
}
2023-09-07 10:22:33 +00:00
type APIDomain struct {
2023-08-05 16:15:52 +00:00
// Id is the Domain's identifier in the database.
Id happydns . Identifier ` json:"id" swaggertype:"string" `
// IdUser is the identifier of the Domain's Owner.
IdUser happydns . Identifier ` json:"id_owner" swaggertype:"string" `
// IsProvider is the identifier of the Provider used to access and edit the
// Domain.
IdProvider happydns . Identifier ` json:"id_provider" swaggertype:"string" `
// DomainName is the FQDN of the managed Domain.
DomainName string ` json:"domain" `
// Group is a hint string aims to group domains.
Group string ` json:"group,omitempty" `
// ZoneHistory are the metadata associated to each Zone saved with the
// current Domain.
2020-06-24 15:33:34 +00:00
ZoneHistory [ ] happydns . ZoneMeta ` json:"zone_history" `
}
2023-08-05 16:15:52 +00:00
// GetDomain retrieves information about a given Domain owned by the user.
//
// @Summary Retrieve Domain local information.
// @Schemes
// @Description Retrieve information in the database about a given Domain owned by the user.
// @Tags domains
// @Accept json
// @Produce json
// @Param domainId path string true "Domain identifier"
// @Security securitydefinitions.basic
2023-09-07 10:22:33 +00:00
// @Success 200 {object} APIDomain
2023-08-05 16:15:52 +00:00
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Domain not found"
// @Router /domains/{domainId} [get]
2021-05-05 01:48:16 +00:00
func GetDomain ( c * gin . Context ) {
domain := c . MustGet ( "domain" ) . ( * happydns . Domain )
2023-09-07 10:22:33 +00:00
ret := & APIDomain {
2021-05-05 01:48:16 +00:00
Id : domain . Id ,
IdUser : domain . IdUser ,
2021-05-22 00:02:24 +00:00
IdProvider : domain . IdProvider ,
2021-05-05 01:48:16 +00:00
DomainName : domain . DomainName ,
2020-06-24 15:33:34 +00:00
ZoneHistory : [ ] happydns . ZoneMeta { } ,
2021-10-27 07:57:24 +00:00
Group : domain . Group ,
2020-06-24 15:33:34 +00:00
}
2021-05-05 01:48:16 +00:00
for _ , zm := range domain . ZoneHistory {
2020-06-24 15:33:34 +00:00
zoneMeta , err := storage . MainStore . GetZoneMeta ( zm )
if err != nil {
2023-05-20 10:43:32 +00:00
log . Printf ( "%s: An error occurs in getDomain, when retrieving a meta history: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
} else {
ret . ZoneHistory = append ( ret . ZoneHistory , * zoneMeta )
2020-06-24 15:33:34 +00:00
}
}
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , ret )
}
2023-08-05 16:15:52 +00:00
// UpdateDomain updates the information about a given Domain owned by the user.
//
// @Summary Update Domain local information.
// @Schemes
// @Description Updates the information about a given Domain owned by the user.
// @Tags domains
// @Accept json
// @Produce json
// @Param domainId path string true "Domain identifier"
// @Param body body happydns.Domain true "The new object overriding the current domain"
// @Security securitydefinitions.basic
// @Success 200 {object} happydns.Domain
// @Failure 400 {object} happydns.Error "Invalid input"
// @Failure 400 {object} happydns.Error "Identifier changed"
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Domain not found"
// @Failure 500 {object} happydns.Error "Database writing error"
// @Router /domains/{domainId} [put]
2021-10-27 07:57:24 +00:00
func UpdateDomain ( c * gin . Context ) {
old := c . MustGet ( "domain" ) . ( * happydns . Domain )
2023-03-10 06:49:49 +00:00
var domain happydns . Domain
2021-10-27 07:57:24 +00:00
err := c . ShouldBindJSON ( & domain )
if err != nil {
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : err . Error ( ) } )
return
}
2022-10-25 16:16:34 +00:00
if ! old . Id . Equals ( domain . Id ) {
2021-10-27 07:57:24 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : "You cannot change the domain reserved ID" } )
return
}
old . Group = domain . Group
err = storage . MainStore . UpdateDomain ( old )
if err != nil {
log . Printf ( "%s: Unable to UpdateDomain in UpdateDomain: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to update your domain. Please retry later." } )
return
}
c . JSON ( http . StatusOK , old )
}
2023-08-05 16:15:52 +00:00
// delDomain removes a domain from the database.
//
// @Summary Stop managing a Domain.
// @Schemes
// @Description Delete all the information in the database about the given Domain. This only stops happyDomain from managing the Domain, it doesn't do anything on the Provider.
// @Tags domains
// @Accept json
// @Produce json
// @Param domainId path string true "Domain identifier"
// @Security securitydefinitions.basic
// @Success 204 "Domain deleted"
// @Failure 400 {object} happydns.Error "Invalid input"
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Domain not found"
// @Failure 500 {object} happydns.Error "Database writing error"
// @Router /domains/{domainId} [delete]
2021-05-05 01:48:16 +00:00
func delDomain ( c * gin . Context ) {
if err := storage . MainStore . DeleteDomain ( c . MustGet ( "domain" ) . ( * happydns . Domain ) ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s was unable to DeleteDomain: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-21 11:08:18 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : fmt . Sprintf ( "Unable to delete your domain: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-04-22 08:17:16 +00:00
}
2021-05-05 01:48:16 +00:00
2023-08-05 16:15:52 +00:00
c . Status ( http . StatusNoContent )
2020-04-22 08:17:16 +00:00
}
2023-11-24 03:18:36 +00:00
// GetDomainLogs retrieves actions recorded for the domain.
//
// @Summary Retrieve Domain actions history.
// @Schemes
// @Description Retrieve information about the actions performed on the domain by users of happyDomain.
// @Tags domains
// @Accept json
// @Produce json
// @Param domainId path string true "Domain identifier"
// @Security securitydefinitions.basic
// @Success 200 {object} []happydns.DomainLog
// @Failure 401 {object} happydns.Error "Authentication failure"
// @Failure 404 {object} happydns.Error "Domain not found"
// @Router /domains/{domainId}/logs [get]
func GetDomainLogs ( c * gin . Context ) {
domain := c . MustGet ( "domain" ) . ( * happydns . Domain )
logs , err := storage . MainStore . GetDomainLogs ( domain )
if err != nil {
log . Printf ( "%s: An error occurs in GetDomainLogs, when retrieving logs: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Unable to access the domain logs. Please try again later." } )
return
}
// Sort by date
sort . Slice ( logs , func ( i , j int ) bool {
return logs [ i ] . Date . After ( logs [ j ] . Date )
} )
c . JSON ( http . StatusOK , logs )
}