mirror of
https://github.com/owncast/owncast.git
synced 2024-11-28 03:01:32 +03:00
API + Data changes to support split up of stream keys and admin passwords
This commit is contained in:
parent
1645451faa
commit
c9e3ccad45
11 changed files with 116 additions and 37 deletions
|
@ -20,7 +20,8 @@ type Defaults struct {
|
||||||
WebServerPort int
|
WebServerPort int
|
||||||
WebServerIP string
|
WebServerIP string
|
||||||
RTMPServerPort int
|
RTMPServerPort int
|
||||||
StreamKey string
|
AdminPassword string
|
||||||
|
StreamKeys []string
|
||||||
|
|
||||||
YPEnabled bool
|
YPEnabled bool
|
||||||
YPServer string
|
YPServer string
|
||||||
|
@ -42,6 +43,8 @@ func GetDefaults() Defaults {
|
||||||
Summary: "This is a new live video streaming server powered by Owncast.",
|
Summary: "This is a new live video streaming server powered by Owncast.",
|
||||||
ServerWelcomeMessage: "",
|
ServerWelcomeMessage: "",
|
||||||
Logo: "logo.svg",
|
Logo: "logo.svg",
|
||||||
|
AdminPassword: "abc123",
|
||||||
|
StreamKeys: []string{"abc123"},
|
||||||
Tags: []string{
|
Tags: []string{
|
||||||
"owncast",
|
"owncast",
|
||||||
"streaming",
|
"streaming",
|
||||||
|
@ -71,7 +74,6 @@ func GetDefaults() Defaults {
|
||||||
WebServerPort: 8080,
|
WebServerPort: 8080,
|
||||||
WebServerIP: "0.0.0.0",
|
WebServerIP: "0.0.0.0",
|
||||||
RTMPServerPort: 1935,
|
RTMPServerPort: 1935,
|
||||||
StreamKey: "abc123",
|
|
||||||
|
|
||||||
ChatEstablishedUserModeTimeDuration: time.Minute * 15,
|
ChatEstablishedUserModeTimeDuration: time.Minute * 15,
|
||||||
|
|
||||||
|
|
|
@ -198,8 +198,8 @@ func SetExtraPageContent(w http.ResponseWriter, r *http.Request) {
|
||||||
controllers.WriteSimpleResponse(w, true, "changed")
|
controllers.WriteSimpleResponse(w, true, "changed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetStreamKey will handle the web config request to set the server stream key.
|
// SetAdminPassword will handle the web config request to set the server admin password.
|
||||||
func SetStreamKey(w http.ResponseWriter, r *http.Request) {
|
func SetAdminPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
if !requirePOST(w, r) {
|
if !requirePOST(w, r) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -209,7 +209,7 @@ func SetStreamKey(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := data.SetStreamKey(configValue.Value.(string)); err != nil {
|
if err := data.SetAdminPassword(configValue.Value.(string)); err != nil {
|
||||||
controllers.WriteSimpleResponse(w, false, err.Error())
|
controllers.WriteSimpleResponse(w, false, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -789,3 +789,27 @@ func getValuesFromRequest(w http.ResponseWriter, r *http.Request) ([]ConfigValue
|
||||||
|
|
||||||
return values, true
|
return values, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetStreamKeys will set the valid stream keys.
|
||||||
|
func SetStreamKeys(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !requirePOST(w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configValues, success := getValuesFromRequest(w, r)
|
||||||
|
if !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
streamKeyStrings := make([]string, 0)
|
||||||
|
for _, key := range configValues {
|
||||||
|
streamKeyStrings = append(streamKeyStrings, key.Value.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := data.SetStreamKeys(streamKeyStrings); err != nil {
|
||||||
|
controllers.WriteSimpleResponse(w, false, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
controllers.WriteSimpleResponse(w, true, "changed")
|
||||||
|
}
|
||||||
|
|
|
@ -49,7 +49,8 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
AppearanceVariables: data.GetCustomColorVariableValues(),
|
AppearanceVariables: data.GetCustomColorVariableValues(),
|
||||||
},
|
},
|
||||||
FFmpegPath: ffmpeg,
|
FFmpegPath: ffmpeg,
|
||||||
StreamKey: data.GetStreamKey(),
|
AdminPassword: data.GetAdminPassword(),
|
||||||
|
StreamKeys: data.GetStreamKeys(),
|
||||||
WebServerPort: config.WebServerPort,
|
WebServerPort: config.WebServerPort,
|
||||||
WebServerIP: config.WebServerIP,
|
WebServerIP: config.WebServerIP,
|
||||||
RTMPServerPort: data.GetRTMPPortNumber(),
|
RTMPServerPort: data.GetRTMPPortNumber(),
|
||||||
|
@ -98,7 +99,8 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
type serverConfigAdminResponse struct {
|
type serverConfigAdminResponse struct {
|
||||||
InstanceDetails webConfigResponse `json:"instanceDetails"`
|
InstanceDetails webConfigResponse `json:"instanceDetails"`
|
||||||
FFmpegPath string `json:"ffmpegPath"`
|
FFmpegPath string `json:"ffmpegPath"`
|
||||||
StreamKey string `json:"streamKey"`
|
AdminPassword string `json:"adminPassword"`
|
||||||
|
StreamKeys []string `json:"streamKeys"`
|
||||||
WebServerPort int `json:"webServerPort"`
|
WebServerPort int `json:"webServerPort"`
|
||||||
WebServerIP string `json:"webServerIP"`
|
WebServerIP string `json:"webServerIP"`
|
||||||
RTMPServerPort int `json:"rtmpServerPort"`
|
RTMPServerPort int `json:"rtmpServerPort"`
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
const (
|
const (
|
||||||
extraContentKey = "extra_page_content"
|
extraContentKey = "extra_page_content"
|
||||||
streamTitleKey = "stream_title"
|
streamTitleKey = "stream_title"
|
||||||
streamKeyKey = "stream_key"
|
adminPasswordKey = "admin_password_key"
|
||||||
logoPathKey = "logo_path"
|
logoPathKey = "logo_path"
|
||||||
logoUniquenessKey = "logo_uniqueness"
|
logoUniquenessKey = "logo_uniqueness"
|
||||||
serverSummaryKey = "server_summary"
|
serverSummaryKey = "server_summary"
|
||||||
|
@ -68,6 +68,7 @@ const (
|
||||||
hideViewerCountKey = "hide_viewer_count"
|
hideViewerCountKey = "hide_viewer_count"
|
||||||
customOfflineMessageKey = "custom_offline_message"
|
customOfflineMessageKey = "custom_offline_message"
|
||||||
customColorVariableValuesKey = "custom_color_variable_values"
|
customColorVariableValuesKey = "custom_color_variable_values"
|
||||||
|
streamKeysKey = "stream_keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetExtraPageBodyContent will return the user-supplied body content.
|
// GetExtraPageBodyContent will return the user-supplied body content.
|
||||||
|
@ -101,20 +102,15 @@ func SetStreamTitle(title string) error {
|
||||||
return _datastore.SetString(streamTitleKey, title)
|
return _datastore.SetString(streamTitleKey, title)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStreamKey will return the inbound streaming password.
|
// GetAdminPassword will return the admin password.
|
||||||
func GetStreamKey() string {
|
func GetAdminPassword() string {
|
||||||
key, err := _datastore.GetString(streamKeyKey)
|
key, _ := _datastore.GetString(adminPasswordKey)
|
||||||
if err != nil {
|
|
||||||
log.Traceln(streamKeyKey, err)
|
|
||||||
return config.GetDefaults().StreamKey
|
|
||||||
}
|
|
||||||
|
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetStreamKey will set the inbound streaming password.
|
// SetAdminPassword will set the admin password.
|
||||||
func SetStreamKey(key string) error {
|
func SetAdminPassword(key string) error {
|
||||||
return _datastore.SetString(streamKeyKey, key)
|
return _datastore.SetString(adminPasswordKey, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLogoPath will return the path for the logo, relative to webroot.
|
// GetLogoPath will return the path for the logo, relative to webroot.
|
||||||
|
@ -582,10 +578,14 @@ func GetVideoCodec() string {
|
||||||
|
|
||||||
// VerifySettings will perform a sanity check for specific settings values.
|
// VerifySettings will perform a sanity check for specific settings values.
|
||||||
func VerifySettings() error {
|
func VerifySettings() error {
|
||||||
if GetStreamKey() == "" {
|
if len(GetStreamKeys()) == 0 {
|
||||||
return errors.New("no stream key set. Please set one via the admin or command line arguments")
|
return errors.New("no stream key set. Please set one via the admin or command line arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if GetAdminPassword() == "" {
|
||||||
|
return errors.New("no admin password set. Please set one via the admin or command line arguments")
|
||||||
|
}
|
||||||
|
|
||||||
logoPath := GetLogoPath()
|
logoPath := GetLogoPath()
|
||||||
if !utils.DoesFileExists(filepath.Join(config.DataDirectory, logoPath)) {
|
if !utils.DoesFileExists(filepath.Join(config.DataDirectory, logoPath)) {
|
||||||
log.Traceln(logoPath, "not found in the data directory. copying a default logo.")
|
log.Traceln(logoPath, "not found in the data directory. copying a default logo.")
|
||||||
|
@ -944,3 +944,14 @@ func GetCustomColorVariableValues() map[string]string {
|
||||||
values, _ := _datastore.GetStringMap(customColorVariableValuesKey)
|
values, _ := _datastore.GetStringMap(customColorVariableValuesKey)
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetStreamKeys will return valid stream keys.
|
||||||
|
func GetStreamKeys() []string {
|
||||||
|
keys, _ := _datastore.GetStringSlice(streamKeysKey)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStreamKeys will set valid stream keys.
|
||||||
|
func SetStreamKeys(keys []string) error {
|
||||||
|
return _datastore.SetStringSlice(streamKeysKey, keys)
|
||||||
|
}
|
||||||
|
|
|
@ -7,18 +7,23 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
datastoreValuesVersion = 1
|
datastoreValuesVersion = 2
|
||||||
datastoreValueVersionKey = "DATA_STORE_VERSION"
|
datastoreValueVersionKey = "DATA_STORE_VERSION"
|
||||||
)
|
)
|
||||||
|
|
||||||
func migrateDatastoreValues(datastore *Datastore) {
|
func migrateDatastoreValues(datastore *Datastore) {
|
||||||
currentVersion, _ := _datastore.GetNumber(datastoreValueVersionKey)
|
currentVersion, _ := _datastore.GetNumber(datastoreValueVersionKey)
|
||||||
|
if currentVersion == 0 {
|
||||||
|
currentVersion = datastoreValuesVersion
|
||||||
|
}
|
||||||
|
|
||||||
for v := currentVersion; v < datastoreValuesVersion; v++ {
|
for v := currentVersion; v < datastoreValuesVersion; v++ {
|
||||||
log.Tracef("Migration datastore values from %d to %d\n", int(v), int(v+1))
|
log.Infof("Migration datastore values from %d to %d\n", int(v), int(v+1))
|
||||||
switch v {
|
switch v {
|
||||||
case 0:
|
case 0:
|
||||||
migrateToDatastoreValues1(datastore)
|
migrateToDatastoreValues1(datastore)
|
||||||
|
case 1:
|
||||||
|
migrateToDatastoreValues2(datastore)
|
||||||
default:
|
default:
|
||||||
log.Fatalln("missing datastore values migration step")
|
log.Fatalln("missing datastore values migration step")
|
||||||
}
|
}
|
||||||
|
@ -47,3 +52,9 @@ func migrateToDatastoreValues1(datastore *Datastore) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migrateToDatastoreValues2(datastore *Datastore) {
|
||||||
|
oldAdminPassword, _ := datastore.GetString("stream_key")
|
||||||
|
_ = SetAdminPassword(oldAdminPassword)
|
||||||
|
_ = SetStreamKeys([]string{oldAdminPassword})
|
||||||
|
}
|
||||||
|
|
|
@ -32,7 +32,8 @@ func PopulateDefaults() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = SetStreamKey(defaults.StreamKey)
|
_ = SetAdminPassword(defaults.AdminPassword)
|
||||||
|
_ = SetStreamKeys(defaults.StreamKeys)
|
||||||
_ = SetHTTPPortNumber(float64(defaults.WebServerPort))
|
_ = SetHTTPPortNumber(float64(defaults.WebServerPort))
|
||||||
_ = SetRTMPPortNumber(float64(defaults.RTMPServerPort))
|
_ = SetRTMPPortNumber(float64(defaults.RTMPServerPort))
|
||||||
_ = SetLogoPath(defaults.Logo)
|
_ = SetLogoPath(defaults.Logo)
|
||||||
|
@ -40,7 +41,6 @@ func PopulateDefaults() {
|
||||||
_ = SetServerSummary(defaults.Summary)
|
_ = SetServerSummary(defaults.Summary)
|
||||||
_ = SetServerWelcomeMessage("")
|
_ = SetServerWelcomeMessage("")
|
||||||
_ = SetServerName(defaults.Name)
|
_ = SetServerName(defaults.Name)
|
||||||
_ = SetStreamKey(defaults.StreamKey)
|
|
||||||
_ = SetExtraPageBodyContent(defaults.PageBodyContent)
|
_ = SetExtraPageBodyContent(defaults.PageBodyContent)
|
||||||
_ = SetFederationGoLiveMessage(defaults.FederationGoLiveMessage)
|
_ = SetFederationGoLiveMessage(defaults.FederationGoLiveMessage)
|
||||||
_ = SetSocialHandles([]models.SocialHandle{
|
_ = SetSocialHandles([]models.SocialHandle{
|
||||||
|
|
|
@ -15,15 +15,17 @@ import (
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _hasInboundRTMPConnection = false
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_hasInboundRTMPConnection = false
|
_pipe *io.PipeWriter
|
||||||
|
_rtmpConnection net.Conn
|
||||||
)
|
)
|
||||||
|
|
||||||
var _pipe *io.PipeWriter
|
var (
|
||||||
var _rtmpConnection net.Conn
|
_setStreamAsConnected func(*io.PipeReader)
|
||||||
|
_setBroadcaster func(models.Broadcaster)
|
||||||
var _setStreamAsConnected func(*io.PipeReader)
|
)
|
||||||
var _setBroadcaster func(models.Broadcaster)
|
|
||||||
|
|
||||||
// Start starts the rtmp service, listening on specified RTMP port.
|
// Start starts the rtmp service, listening on specified RTMP port.
|
||||||
func Start(setStreamAsConnected func(*io.PipeReader), setBroadcaster func(models.Broadcaster)) {
|
func Start(setStreamAsConnected func(*io.PipeReader), setBroadcaster func(models.Broadcaster)) {
|
||||||
|
@ -75,12 +77,28 @@ func HandleConn(c *rtmp.Conn, nc net.Conn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !secretMatch(data.GetStreamKey(), c.URL.Path) {
|
accessGranted := false
|
||||||
|
validStreamingKeys := data.GetStreamKeys()
|
||||||
|
|
||||||
|
for _, key := range validStreamingKeys {
|
||||||
|
if secretMatch(key, c.URL.Path) {
|
||||||
|
accessGranted = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !accessGranted {
|
||||||
log.Errorln("invalid streaming key; rejecting incoming stream")
|
log.Errorln("invalid streaming key; rejecting incoming stream")
|
||||||
_ = nc.Close()
|
_ = nc.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if !secretMatch(data.GetAdminPassword(), c.URL.Path) {
|
||||||
|
// log.Errorln("invalid streaming key; rejecting incoming stream")
|
||||||
|
// _ = nc.Close()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
rtmpOut, rtmpIn := io.Pipe()
|
rtmpOut, rtmpIn := io.Pipe()
|
||||||
_pipe = rtmpIn
|
_pipe = rtmpIn
|
||||||
log.Infoln("Inbound stream connected.")
|
log.Infoln("Inbound stream connected.")
|
||||||
|
|
6
main.go
6
main.go
|
@ -42,7 +42,7 @@ func main() {
|
||||||
|
|
||||||
// Create the data directory if needed
|
// Create the data directory if needed
|
||||||
if !utils.DoesFileExists("data") {
|
if !utils.DoesFileExists("data") {
|
||||||
if err := os.Mkdir("./data", 0700); err != nil {
|
if err := os.Mkdir("./data", 0o700); err != nil {
|
||||||
log.Fatalln("Cannot create data directory", err)
|
log.Fatalln("Cannot create data directory", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ func main() {
|
||||||
log.Fatalln("Unable to remove temp dir!")
|
log.Fatalln("Unable to remove temp dir!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := os.Mkdir(config.TempDir, 0700); err != nil {
|
if err := os.Mkdir(config.TempDir, 0o700); err != nil {
|
||||||
log.Fatalln("Unable to create temp dir!", err)
|
log.Fatalln("Unable to create temp dir!", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ func main() {
|
||||||
|
|
||||||
func handleCommandLineFlags() {
|
func handleCommandLineFlags() {
|
||||||
if *newStreamKey != "" {
|
if *newStreamKey != "" {
|
||||||
if err := data.SetStreamKey(*newStreamKey); err != nil {
|
if err := data.SetAdminPassword(*newStreamKey); err != nil {
|
||||||
log.Errorln("Error setting your stream key.", err)
|
log.Errorln("Error setting your stream key.", err)
|
||||||
log.Exit(1)
|
log.Exit(1)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -22,7 +22,7 @@ type UserAccessTokenHandlerFunc func(user.User, http.ResponseWriter, *http.Reque
|
||||||
func RequireAdminAuth(handler http.HandlerFunc) http.HandlerFunc {
|
func RequireAdminAuth(handler http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
username := "admin"
|
username := "admin"
|
||||||
password := data.GetStreamKey()
|
password := data.GetAdminPassword()
|
||||||
realm := "Owncast Authenticated Request"
|
realm := "Owncast Authenticated Request"
|
||||||
|
|
||||||
// The following line is kind of a work around.
|
// The following line is kind of a work around.
|
||||||
|
|
|
@ -159,7 +159,10 @@ func Start() error {
|
||||||
// Update config values
|
// Update config values
|
||||||
|
|
||||||
// Change the current streaming key in memory
|
// Change the current streaming key in memory
|
||||||
http.HandleFunc("/api/admin/config/key", middleware.RequireAdminAuth(admin.SetStreamKey))
|
http.HandleFunc("/api/admin/config/adminpass", middleware.RequireAdminAuth(admin.SetAdminPassword))
|
||||||
|
|
||||||
|
// Set an array of valid stream keys
|
||||||
|
http.HandleFunc("/api/admin/config/streamkeys", middleware.RequireAdminAuth(admin.SetStreamKeys))
|
||||||
|
|
||||||
// Change the extra page content in memory
|
// Change the extra page content in memory
|
||||||
http.HandleFunc("/api/admin/config/pagecontent", middleware.RequireAdminAuth(admin.SetExtraPageContent))
|
http.HandleFunc("/api/admin/config/pagecontent", middleware.RequireAdminAuth(admin.SetExtraPageContent))
|
||||||
|
|
|
@ -7,6 +7,8 @@ const serverSummary = randomString();
|
||||||
const offlineMessage = randomString();
|
const offlineMessage = randomString();
|
||||||
const pageContent = `<p>${randomString()}</p>`;
|
const pageContent = `<p>${randomString()}</p>`;
|
||||||
const tags = [randomString(), randomString(), randomString()];
|
const tags = [randomString(), randomString(), randomString()];
|
||||||
|
const streamKeys = [randomString(), randomString(), randomString()];
|
||||||
|
|
||||||
const latencyLevel = Math.floor(Math.random() * 4);
|
const latencyLevel = Math.floor(Math.random() * 4);
|
||||||
const appearanceValues = {
|
const appearanceValues = {
|
||||||
variable1: randomString(),
|
variable1: randomString(),
|
||||||
|
@ -65,6 +67,11 @@ test('set tags', async (done) => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('set stream keys', async (done) => {
|
||||||
|
const res = await sendConfigChangeRequest('streamkeys', streamKeys);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
test('set latency level', async (done) => {
|
test('set latency level', async (done) => {
|
||||||
const res = await sendConfigChangeRequest(
|
const res = await sendConfigChangeRequest(
|
||||||
'video/streamlatencylevel',
|
'video/streamlatencylevel',
|
||||||
|
@ -157,6 +164,7 @@ test('admin configuration is correct', (done) => {
|
||||||
socialHandles
|
socialHandles
|
||||||
);
|
);
|
||||||
expect(res.body.forbiddenUsernames).toStrictEqual(forbiddenUsernames);
|
expect(res.body.forbiddenUsernames).toStrictEqual(forbiddenUsernames);
|
||||||
|
expect(res.body.streamKeys).toStrictEqual(streamKeys);
|
||||||
|
|
||||||
expect(res.body.videoSettings.latencyLevel).toBe(latencyLevel);
|
expect(res.body.videoSettings.latencyLevel).toBe(latencyLevel);
|
||||||
expect(res.body.videoSettings.videoQualityVariants[0].framerate).toBe(
|
expect(res.body.videoSettings.videoQualityVariants[0].framerate).toBe(
|
||||||
|
@ -167,7 +175,7 @@ test('admin configuration is correct', (done) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(res.body.yp.enabled).toBe(false);
|
expect(res.body.yp.enabled).toBe(false);
|
||||||
expect(res.body.streamKey).toBe('abc123');
|
expect(res.body.adminPassword).toBe('abc123');
|
||||||
|
|
||||||
expect(res.body.s3.enabled).toBe(s3Config.enabled);
|
expect(res.body.s3.enabled).toBe(s3Config.enabled);
|
||||||
expect(res.body.s3.endpoint).toBe(s3Config.endpoint);
|
expect(res.body.s3.endpoint).toBe(s3Config.endpoint);
|
||||||
|
|
Loading…
Reference in a new issue