Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optionally disable standard login #126

Merged
merged 1 commit into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ services:
TOKEN_EXPIRY: 24h
ALLOW_NEW_ACCOUNTS: "true"
DEBUG: "true"
DISABLE_ACCOUNTS: "false"
DISABLE_INTERNAL_ACCOUNTS: "false"

# See https://github.com/jordan-dalby/ByteStash/wiki/Single-Sign%E2%80%90on-Setup for more info
OIDC_ENABLED: "false"
Expand Down
124 changes: 65 additions & 59 deletions client/src/components/auth/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export const LoginPage: React.FC = () => {
window.location.href = `${window.__BASE_PATH__}/api/auth/oidc/auth`;
};

const showInternalRegistration = !authConfig?.disableInternalAccounts;

return (
<PageContainer className="flex items-center justify-center min-h-screen">
<div className="max-w-md w-full space-y-8">
Expand All @@ -75,7 +77,7 @@ export const LoginPage: React.FC = () => {
</h2>
<p className="mt-2 text-center text-sm text-gray-400">
Please sign in to continue
{authConfig?.allowNewAccounts ? (
{authConfig?.allowNewAccounts && showInternalRegistration ? (
<>
, create an{' '}
<Link to="/register" className="text-blue-400 hover:text-blue-300">
Expand All @@ -101,72 +103,76 @@ export const LoginPage: React.FC = () => {
>
Sign in with {oidcConfig.displayName}
</button>
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-700"></div>
</div>
<div className="relative flex justify-center">
<span className="px-2 bg-gray-900 text-gray-500 text-sm">
Or continue with password
</span>
{showInternalRegistration && (
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-700"></div>
</div>
<div className="relative flex justify-center">
<span className="px-2 bg-gray-900 text-gray-500 text-sm">
Or continue with password
</span>
</div>
</div>
</div>
)}
</>
)}

<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
<div className="rounded-md shadow-sm -space-y-px">
<div>
<input
type="text"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border
border-gray-700 placeholder-gray-500 text-white bg-gray-800 rounded-t-md
focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
disabled={isLoading}
/>
{showInternalRegistration && (
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
<div className="rounded-md shadow-sm -space-y-px">
<div>
<input
type="text"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border
border-gray-700 placeholder-gray-500 text-white bg-gray-800 rounded-t-md
focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
disabled={isLoading}
/>
</div>
<div>
<input
type="password"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border
border-gray-700 placeholder-gray-500 text-white bg-gray-800 rounded-b-md
focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoading}
/>
</div>
</div>

<div>
<input
type="password"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border
border-gray-700 placeholder-gray-500 text-white bg-gray-800 rounded-b-md
focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
<button
type="submit"
className="group relative w-full flex justify-center py-2 px-4 border border-transparent
text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
disabled:opacity-50 disabled:cursor-not-allowed"
disabled={isLoading}
/>
>
{isLoading ? (
<span className="flex items-center">
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Signing in...
</span>
) : (
'Sign in'
)}
</button>
</div>
</div>

<div>
<button
type="submit"
className="group relative w-full flex justify-center py-2 px-4 border border-transparent
text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
disabled:opacity-50 disabled:cursor-not-allowed"
disabled={isLoading}
>
{isLoading ? (
<span className="flex items-center">
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Signing in...
</span>
) : (
'Sign in'
)}
</button>
</div>
</form>
</form>
)}
</div>
</PageContainer>
);
Expand Down
170 changes: 96 additions & 74 deletions client/src/components/auth/RegisterPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ export const RegisterPage: React.FC = () => {
window.location.href = `${window.__BASE_PATH__}/api/auth/oidc/auth`;
};

const showInternalRegistration = !authConfig?.disableInternalAccounts;

return (
<PageContainer className="flex items-center justify-center min-h-screen">
<div className="max-w-md w-full space-y-6">
Expand Down Expand Up @@ -137,91 +139,111 @@ export const RegisterPage: React.FC = () => {
>
Sign in with {oidcConfig.displayName}
</button>
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-700"></div>
</div>
<div className="relative flex justify-center">
<span className="px-2 bg-gray-900 text-gray-500 text-sm">
Or continue with password
</span>
{showInternalRegistration && (
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-700"></div>
</div>
<div className="relative flex justify-center">
<span className="px-2 bg-gray-900 text-gray-500 text-sm">
Or continue with password
</span>
</div>
</div>
</div>
)}
</>
)}

<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
<div className="rounded-md shadow-sm -space-y-px">
<div>
<label htmlFor="username" className="sr-only">Username</label>
<input
id="username"
type="text"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border
border-gray-700 placeholder-gray-500 text-white bg-gray-800 rounded-t-md
focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
disabled={isLoading}
/>
</div>
<div>
<label htmlFor="password" className="sr-only">Password</label>
<input
id="password"
type="password"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border
border-gray-700 placeholder-gray-500 text-white bg-gray-800
focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoading}
/>
{showInternalRegistration && (
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
<div className="rounded-md shadow-sm -space-y-px">
<div>
<label htmlFor="username" className="sr-only">Username</label>
<input
id="username"
type="text"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border
border-gray-700 placeholder-gray-500 text-white bg-gray-800 rounded-t-md
focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
disabled={isLoading}
/>
</div>
<div>
<label htmlFor="password" className="sr-only">Password</label>
<input
id="password"
type="password"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border
border-gray-700 placeholder-gray-500 text-white bg-gray-800
focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoading}
/>
</div>
<div>
<label htmlFor="confirm-password" className="sr-only">Confirm Password</label>
<input
id="confirm-password"
type="password"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border
border-gray-700 placeholder-gray-500 text-white bg-gray-800 rounded-b-md
focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Confirm Password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
disabled={isLoading}
/>
</div>
</div>

<div>
<label htmlFor="confirm-password" className="sr-only">Confirm Password</label>
<input
id="confirm-password"
type="password"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border
border-gray-700 placeholder-gray-500 text-white bg-gray-800 rounded-b-md
focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
placeholder="Confirm Password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
<button
type="submit"
className="group relative w-full flex justify-center py-2 px-4 border border-transparent
text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
disabled:opacity-50 disabled:cursor-not-allowed"
disabled={isLoading}
/>
>
{isLoading ? (
<span className="flex items-center">
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Creating Account...
</span>
) : (
'Create Account'
)}
</button>
</div>
</div>
</form>
)}

<div>
<button
type="submit"
className="group relative w-full flex justify-center py-2 px-4 border border-transparent
text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
disabled:opacity-50 disabled:cursor-not-allowed"
disabled={isLoading}
{!showInternalRegistration && !oidcConfig?.enabled && (
<div className="text-center">
<h2 className="text-xl font-bold text-red-400 mb-4">Registration Not Available</h2>
<p className="text-gray-400 mb-4">
Internal account registration is disabled and no SSO providers are configured.
Please contact your administrator.
</p>
<Link
to={ROUTES.PUBLIC_SNIPPETS}
className="text-blue-400 hover:text-blue-300"
>
{isLoading ? (
<span className="flex items-center">
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Creating Account...
</span>
) : (
'Create Account'
)}
</button>
Browse public snippets
</Link>
</div>
</form>
)}
</div>
</PageContainer>
);
Expand Down
1 change: 1 addition & 0 deletions client/src/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export interface AuthConfig {
allowNewAccounts: boolean;
hasUsers: boolean;
disableAccounts: boolean;
disableInternalAccounts: boolean;
}
2 changes: 2 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ services:
- DEBUG=false
# Should we use accounts at all? When enabled, it will be like starting a fresh account so export your snippets, no login required
- DISABLE_ACCOUNTS=false
# Should internal accounts be disabled?
- DISABLE_INTERNAL_ACCOUNTS=false

# Optional: Enable OIDC for Single Sign On
- OIDC_ENABLED=true
Expand Down
2 changes: 2 additions & 0 deletions server/src/middleware/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const JWT_SECRET = getJwtSecret();
const ALLOW_NEW_ACCOUNTS = process.env.ALLOW_NEW_ACCOUNTS === 'true';
const TOKEN_EXPIRY = process.env.TOKEN_EXPIRY || '24h';
const DISABLE_ACCOUNTS = process.env.DISABLE_ACCOUNTS === 'true';
const DISABLE_INTERNAL_ACCOUNTS = process.env.DISABLE_INTERNAL_ACCOUNTS === 'true';

function generateAnonymousUsername() {
return `anon-${crypto.randomBytes(8).toString('hex')}`;
Expand Down Expand Up @@ -74,5 +75,6 @@ export {
TOKEN_EXPIRY,
ALLOW_NEW_ACCOUNTS,
DISABLE_ACCOUNTS,
DISABLE_INTERNAL_ACCOUNTS,
getOrCreateAnonymousUser
};
Loading