Add user setup config
This commit is contained in:
parent
66f15b49b2
commit
342ff60b70
7 changed files with 230 additions and 18 deletions
|
|
@ -43,9 +43,16 @@ export const Landing: React.FC<LandingProps> = ({ onGenerate, onCreateManual, on
|
|||
};
|
||||
|
||||
const handleLoadQuiz = async (id: string) => {
|
||||
try {
|
||||
const quiz = await loadQuiz(id);
|
||||
setLibraryOpen(false);
|
||||
onLoadQuiz(quiz);
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.includes('redirecting')) {
|
||||
return;
|
||||
}
|
||||
console.error('Failed to load quiz:', err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ services:
|
|||
OIDC_ISSUER: http://localhost:9000/application/o/kaboot/
|
||||
OIDC_JWKS_URI: http://localhost:9000/application/o/kaboot/jwks/
|
||||
OIDC_INTERNAL_JWKS_URI: http://authentik-server:9000/application/o/kaboot/jwks/
|
||||
CORS_ORIGIN: ${CORS_ORIGIN:-http://localhost:3000}
|
||||
CORS_ORIGIN: ${CORS_ORIGIN:-http://localhost:5173}
|
||||
volumes:
|
||||
- kaboot-data:/data
|
||||
ports:
|
||||
|
|
|
|||
|
|
@ -70,7 +70,168 @@ This guide walks through configuring Authentik as the OAuth2/OIDC identity provi
|
|||
|
||||
7. Click **Submit**
|
||||
|
||||
## Step 4: Verify OIDC Endpoints
|
||||
## Step 4: Enable User Registration (Sign Up)
|
||||
|
||||
By default, Authentik only shows a login form. To allow users to sign up, you need to create an enrollment flow and link it.
|
||||
|
||||
### Step 4.1: Create the Enrollment Prompt Stage
|
||||
|
||||
1. Go to **Flows and Stages** > **Stages**
|
||||
|
||||
2. Click **Create**
|
||||
|
||||
3. Select **Prompt Stage** and click **Next**
|
||||
|
||||
4. Configure:
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Name | `enrollment-prompt` |
|
||||
|
||||
5. In the **Fields** section, move these to the **Selected** side:
|
||||
- `default-source-enrollment-field-username` (username)
|
||||
- `default-user-settings-field-email` (email)
|
||||
- `default-password-change-field-password` (password)
|
||||
- `default-password-change-field-password-repeat` (password_repeat)
|
||||
|
||||
6. (Optional) In **Validation policies**, select `password-complexity` if you created it in Step 4.2
|
||||
|
||||
7. Click **Finish**
|
||||
|
||||
### Step 4.2: (Optional) Create Password Complexity Policy
|
||||
|
||||
1. Go to **Customisation** > **Policies**
|
||||
|
||||
2. Click **Create** and select **Password Policy**
|
||||
|
||||
3. Configure:
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Name | `password-complexity` |
|
||||
| Password field | `password` |
|
||||
| Minimum length | `8` |
|
||||
| Amount of uppercase characters | `1` |
|
||||
| Amount of lowercase characters | `1` |
|
||||
| Amount of digits | `1` |
|
||||
|
||||
4. Click **Finish**
|
||||
|
||||
You'll add this to the enrollment prompt stage later.
|
||||
|
||||
### Step 4.3: Create a Group for Kaboot Users
|
||||
|
||||
1. Go to **Directory** > **Groups**
|
||||
|
||||
2. Click **Create**
|
||||
|
||||
3. Configure:
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Name | `kaboot-users` |
|
||||
|
||||
4. Click **Create**
|
||||
|
||||
### Step 4.4: Create the User Write Stage
|
||||
|
||||
1. Go to **Flows and Stages** > **Stages**
|
||||
|
||||
2. Click **Create**
|
||||
|
||||
3. Select **User Write Stage** and click **Next**
|
||||
|
||||
4. Configure:
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Name | `enrollment-user-write` |
|
||||
| User creation mode | `Create users when required` |
|
||||
| Create users as inactive | Unchecked |
|
||||
| Group | `kaboot-users` |
|
||||
|
||||
5. Click **Finish**
|
||||
|
||||
### Step 4.5: Create the User Login Stage
|
||||
|
||||
1. Go to **Flows and Stages** > **Stages**
|
||||
|
||||
2. Click **Create**
|
||||
|
||||
3. Select **User Login Stage** and click **Next**
|
||||
|
||||
4. Configure:
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Name | `enrollment-user-login` |
|
||||
| Session duration | `hours=24` |
|
||||
| Stay signed in offset | `days=30` |
|
||||
| Network binding | `No binding` |
|
||||
| GeoIP binding | `No binding` |
|
||||
|
||||
5. Click **Finish**
|
||||
|
||||
### Step 4.6: Create the Enrollment Flow
|
||||
|
||||
1. Go to **Flows and Stages** > **Flows**
|
||||
|
||||
2. Click **Create**
|
||||
|
||||
3. Configure:
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Name | `Enrollment Flow` |
|
||||
| Title | `Sign Up` |
|
||||
| Slug | `enrollment-flow` |
|
||||
| Designation | `Enrollment` |
|
||||
| Authentication | `No requirement` |
|
||||
|
||||
4. Click **Create**
|
||||
|
||||
5. Click on the newly created `enrollment-flow`
|
||||
|
||||
6. Go to the **Stage Bindings** tab
|
||||
|
||||
7. Click **Bind existing stage** and add stages in this order:
|
||||
| Stage | Order |
|
||||
|-------|-------|
|
||||
| `enrollment-prompt` | 10 |
|
||||
| `enrollment-user-write` | 20 |
|
||||
| `enrollment-user-login` | 30 |
|
||||
|
||||
### Step 4.7: Bind the Group to the Kaboot Application
|
||||
|
||||
1. Go to **Applications** > **Applications** > **Kaboot**
|
||||
|
||||
2. Go to the **Policy / Group / User Bindings** tab
|
||||
|
||||
3. Click **Bind existing group**
|
||||
|
||||
4. Select `kaboot-users`
|
||||
|
||||
5. Click **Bind**
|
||||
|
||||
Now users in the `kaboot-users` group (which includes all users who sign up) will have access to Kaboot.
|
||||
|
||||
### Step 4.8: Link Enrollment Flow to Login
|
||||
|
||||
1. Go to **Flows and Stages** > **Stages**
|
||||
|
||||
2. Find and click on `default-authentication-identification`
|
||||
|
||||
3. Scroll down to **Flow settings**
|
||||
|
||||
4. In the **Enrollment flow** dropdown, select `enrollment-flow`
|
||||
|
||||
5. Click **Update**
|
||||
|
||||
Now when users visit the login page, they'll see a "Need an account? Sign up." link.
|
||||
|
||||
### Optional: Add Password Recovery
|
||||
|
||||
1. In **Flows and Stages** > **Stages** > `default-authentication-identification`
|
||||
|
||||
2. Set **Recovery flow** to `default-recovery-flow` (if it exists)
|
||||
|
||||
3. Click **Update**
|
||||
|
||||
## Step 5: Verify OIDC Endpoints
|
||||
|
||||
After creation, go to **Applications** > **Providers** > **Kaboot OAuth2**
|
||||
|
||||
|
|
|
|||
|
|
@ -6,35 +6,70 @@ const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001';
|
|||
export const useAuthenticatedFetch = () => {
|
||||
const auth = useAuth();
|
||||
|
||||
const authFetch = useCallback(
|
||||
async (path: string, options: RequestInit = {}): Promise<Response> => {
|
||||
const isTokenExpired = useCallback(() => {
|
||||
if (!auth.user?.expires_at) return true;
|
||||
const expiresAt = auth.user.expires_at * 1000;
|
||||
const now = Date.now();
|
||||
const bufferMs = 60 * 1000;
|
||||
return now >= expiresAt - bufferMs;
|
||||
}, [auth.user?.expires_at]);
|
||||
|
||||
const ensureValidToken = useCallback(async (): Promise<string> => {
|
||||
if (!auth.user?.access_token) {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
|
||||
if (isTokenExpired()) {
|
||||
try {
|
||||
const user = await auth.signinSilent();
|
||||
if (user?.access_token) {
|
||||
return user.access_token;
|
||||
}
|
||||
} catch {
|
||||
auth.signinRedirect();
|
||||
throw new Error('Session expired, redirecting to login');
|
||||
}
|
||||
}
|
||||
|
||||
return auth.user.access_token;
|
||||
}, [auth, isTokenExpired]);
|
||||
|
||||
const authFetch = useCallback(
|
||||
async (path: string, options: RequestInit = {}): Promise<Response> => {
|
||||
const token = await ensureValidToken();
|
||||
const url = path.startsWith('http') ? path : `${API_URL}${path}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
Authorization: `Bearer ${auth.user.access_token}`,
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
try {
|
||||
await auth.signinSilent();
|
||||
const user = await auth.signinSilent();
|
||||
if (user?.access_token) {
|
||||
return fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
Authorization: `Bearer ${user.access_token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
auth.signinRedirect();
|
||||
}
|
||||
throw new Error('Token expired, please retry');
|
||||
throw new Error('Session expired, redirecting to login');
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
[auth]
|
||||
[auth, ensureValidToken]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
|||
12
index.tsx
12
index.tsx
|
|
@ -9,10 +9,20 @@ if (!rootElement) {
|
|||
throw new Error("Could not find root element to mount to");
|
||||
}
|
||||
|
||||
const onSigninCallback = () => {
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
};
|
||||
|
||||
const root = ReactDOM.createRoot(rootElement);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<AuthProvider {...oidcConfig}>
|
||||
<AuthProvider
|
||||
{...oidcConfig}
|
||||
onSigninCallback={onSigninCallback}
|
||||
onRemoveUser={() => {
|
||||
window.localStorage.clear();
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</AuthProvider>
|
||||
</React.StrictMode>
|
||||
|
|
|
|||
|
|
@ -12,9 +12,8 @@ export const oidcConfig = {
|
|||
response_type: 'code',
|
||||
scope: 'openid profile email offline_access',
|
||||
automaticSilentRenew: true,
|
||||
silentRequestTimeoutInSeconds: 10,
|
||||
loadUserInfo: true,
|
||||
userStore: new WebStorageStateStore({ store: window.localStorage }),
|
||||
onSigninCallback: () => {
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
},
|
||||
monitorSession: false,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export default defineConfig(({ mode }) => {
|
|||
const env = loadEnv(mode, '.', '');
|
||||
return {
|
||||
server: {
|
||||
port: 3000,
|
||||
port: 5173,
|
||||
host: '0.0.0.0',
|
||||
},
|
||||
plugins: [react()],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue