fix: remove special character requirement from stream keys (#4754)

Some broadcasting software (e.g., Prism Live Studio) strips special
characters from stream keys, making Owncast-generated keys incompatible.

Changes:
- Create separate STREAM_KEY_COMPLEXITY_RULES without special char requirement
- Update generateRndKey() to only use alphanumeric characters
- Keep PASSWORD_COMPLEXITY_RULES unchanged for admin password security
- Update tooltip text to reflect new requirements
- Add comprehensive tests for stream key generation

Fixes #4690

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John Costa
2026-01-28 11:51:57 -08:00
committed by GitHub
co-authored by Claude Opus 4.5
parent 93b482871f
commit 397ec95d9b
3 changed files with 64 additions and 20 deletions
@@ -1,10 +1,10 @@
import React, { useContext, useEffect, useState } from 'react';
import React, { useContext, useState } from 'react';
import { Table, Space, Button, Typography, Alert, Input, Form, message } from 'antd';
import dynamic from 'next/dynamic';
import { ServerStatusContext } from '../../../../utils/server-status-context';
import { fetchData, UPDATE_STREAM_KEYS } from '../../../../utils/apis';
import { PASSWORD_COMPLEXITY_RULES, REGEX_PASSWORD } from '../../../../utils/config-constants';
import { STREAM_KEY_COMPLEXITY_RULES, REGEX_STREAM_KEY } from '../../../../utils/config-constants';
const { Paragraph } = Typography;
@@ -37,14 +37,13 @@ const saveKeys = async (keys, setError) => {
export const generateRndKey = () => {
let defaultKey = '';
let isValidStreamKey = false;
const streamKeyRegex = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[!@#$^&*]).{8,192}$/;
const s = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$^&*';
const s = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
while (!isValidStreamKey) {
const temp = Array.apply(20, Array(30))
.map(() => s.charAt(Math.floor(Math.random() * s.length)))
.join('');
if (streamKeyRegex.test(temp)) {
if (REGEX_STREAM_KEY.test(temp)) {
isValidStreamKey = true;
defaultKey = temp;
}
@@ -57,15 +56,6 @@ const AddKeyForm = ({ setShowAddKeyForm, setFieldInConfigState, streamKeys, setE
const [form] = Form.useForm();
const { Item } = Form;
// Password Complexity rules
const passwordComplexityRules = [];
useEffect(() => {
PASSWORD_COMPLEXITY_RULES.forEach(element => {
passwordComplexityRules.push(element);
});
}, []);
const handleAddKey = (newkey: any) => {
const updatedKeys = [...streamKeys, newkey];
@@ -81,7 +71,7 @@ const AddKeyForm = ({ setShowAddKeyForm, setFieldInConfigState, streamKeys, setE
const handleInputChange = (event: any) => {
const val = event.target.value;
if (REGEX_PASSWORD.test(val)) {
if (REGEX_STREAM_KEY.test(val)) {
setHasChanged(true);
} else {
setHasChanged(false);
@@ -108,10 +98,10 @@ const AddKeyForm = ({ setShowAddKeyForm, setFieldInConfigState, streamKeys, setE
<p>
The key you provide your broadcasting software. Please note that the key must be a
minimum of eight characters and must include at least one uppercase letter, at least one
lowercase letter, at least one special character, and at least one number.
lowercase letter, and at least one number.
</p>
}
rules={PASSWORD_COMPLEXITY_RULES}
rules={STREAM_KEY_COMPLEXITY_RULES}
>
<Input placeholder="your key" onChange={handleInputChange} />
</Item>
+32 -3
View File
@@ -1,10 +1,23 @@
import { generateRndKey } from '../components/admin/config/server/StreamKeys';
import { REGEX_STREAM_KEY } from '../utils/config-constants';
describe('generateRndKey', () => {
test('should generate a key that matches the regular expression', () => {
test('should generate a key that matches the stream key regex', () => {
const key = generateRndKey();
const regex = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[!@#$^&*]).{8,192}$/;
expect(regex.test(key)).toBe(true);
// Use the same regex constant that the implementation uses
expect(REGEX_STREAM_KEY.test(key)).toBe(true);
});
test('should only contain alphanumeric characters (no special characters for broadcasting software compatibility)', () => {
const key = generateRndKey();
// Keys should be purely alphanumeric for compatibility with broadcasting software
expect(key).toMatch(/^[a-zA-Z0-9]+$/);
});
test('should not contain dashes', () => {
const key = generateRndKey();
// Dashes are explicitly forbidden as they can break RTMP URL parsing
expect(key).not.toContain('-');
});
test('returns a string', () => {
@@ -23,4 +36,20 @@ describe('generateRndKey', () => {
const key2 = generateRndKey();
expect(key1).not.toBe(key2);
});
test('should contain at least one uppercase letter, one lowercase letter, and one digit', () => {
const key = generateRndKey();
expect(key).toMatch(/[A-Z]/); // has uppercase
expect(key).toMatch(/[a-z]/); // has lowercase
expect(key).toMatch(/[0-9]/); // has digit
});
test('should consistently generate valid keys across multiple invocations', () => {
// Generate multiple keys to ensure consistency
for (let i = 0; i < 10; i++) {
const key = generateRndKey();
expect(REGEX_STREAM_KEY.test(key)).toBe(true);
expect(key).toMatch(/^[a-zA-Z0-9]+$/);
}
});
});
+25
View File
@@ -639,3 +639,28 @@ export const PASSWORD_COMPLEXITY_RULES = [
];
export const REGEX_PASSWORD = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[!@#$%^&*])[^-]{8,192}$/;
// Stream key validation rules - same as password but WITHOUT special character requirement
// This is needed because some broadcasting software (e.g., Prism Live Studio) strips special characters
export const STREAM_KEY_COMPLEXITY_RULES = [
{ min: 8, message: '- minimum 8 characters' },
{ max: 192, message: '- maximum 192 characters' },
{
pattern: /^(?=.*[a-z])/,
message: '- at least one lowercase letter',
},
{
pattern: /^(?=.*[A-Z])/,
message: '- at least one uppercase letter',
},
{
pattern: /\d/,
message: '- at least one digit',
},
{
pattern: /^[^-]+$/,
message: '- must NOT contain a dash: -',
},
];
export const REGEX_STREAM_KEY = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])[^-]{8,192}$/;