I can't believe it but I have sign in with Microsoft working!
This commit is contained in:
@@ -3,13 +3,11 @@ import { z } from 'zod';
|
|||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
|
||||||
import { useAuthActions } from '@convex-dev/auth/react';
|
import { useAuthActions } from '@convex-dev/auth/react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { ConvexError } from 'convex/values';
|
import { ConvexError } from 'convex/values';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
Form,
|
Form,
|
||||||
@@ -27,6 +25,10 @@ import {
|
|||||||
TabsTrigger,
|
TabsTrigger,
|
||||||
} from '@/components/ui';
|
} from '@/components/ui';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import {
|
||||||
|
GibsAuthSignInButton,
|
||||||
|
MicrosoftSignInButton
|
||||||
|
} from '@/components/layout/auth/buttons';
|
||||||
import { PASSWORD_MIN, PASSWORD_MAX, PASSWORD_REGEX } from '@/lib/types';
|
import { PASSWORD_MIN, PASSWORD_MAX, PASSWORD_REGEX } from '@/lib/types';
|
||||||
|
|
||||||
const signInFormSchema = z.object({
|
const signInFormSchema = z.object({
|
||||||
@@ -228,24 +230,7 @@ const SignIn = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex my-auto justify-center'>
|
<div className='flex my-auto justify-center'>
|
||||||
<Button
|
<MicrosoftSignInButton />
|
||||||
size='lg'
|
|
||||||
onClick={() => signIn('authentik')}
|
|
||||||
className='text-lg font-semibold w-5/6 mx-auto'
|
|
||||||
>
|
|
||||||
<div className='flex flex-row space-x-2 my-auto'>
|
|
||||||
<Image
|
|
||||||
src={'/icons/misc/gibs-auth-logo.png'}
|
|
||||||
className=''
|
|
||||||
alt='Gib's Auth'
|
|
||||||
width={30}
|
|
||||||
height={30}
|
|
||||||
/>
|
|
||||||
<p className='my-auto'>
|
|
||||||
Sign In with Gib's Auth
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -353,24 +338,7 @@ const SignIn = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex my-auto justify-center'>
|
<div className='flex my-auto justify-center'>
|
||||||
<Button
|
<MicrosoftSignInButton type='signUp' />
|
||||||
size='lg'
|
|
||||||
onClick={() => signIn('authentik')}
|
|
||||||
className='text-lg font-semibold w-5/6 mx-auto'
|
|
||||||
>
|
|
||||||
<div className='flex flex-row space-x-2 my-auto'>
|
|
||||||
<Image
|
|
||||||
src={'/icons/misc/gibs-auth-logo.png'}
|
|
||||||
className=''
|
|
||||||
alt='Gib's Auth'
|
|
||||||
width={30}
|
|
||||||
height={30}
|
|
||||||
/>
|
|
||||||
<p className='my-auto'>
|
|
||||||
Sign Up with Gib's Auth
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
41
apps/next/src/components/layout/auth/buttons/gibs-auth.tsx
Normal file
41
apps/next/src/components/layout/auth/buttons/gibs-auth.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import Image from 'next/image';
|
||||||
|
import { useAuthActions } from '@convex-dev/auth/react';
|
||||||
|
import { Button, type buttonVariants } from '@/components/ui';
|
||||||
|
import { type ComponentProps } from 'react';
|
||||||
|
import { type VariantProps } from 'class-variance-authority';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
buttonProps?: Omit<ComponentProps<'button'>, 'onClick'> &
|
||||||
|
VariantProps<typeof buttonVariants> & {
|
||||||
|
asChild?: boolean;
|
||||||
|
},
|
||||||
|
type?: 'signIn' | 'signUp';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GibsAuthSignInButton = ({
|
||||||
|
buttonProps,
|
||||||
|
type = 'signIn',
|
||||||
|
}: Props) => {
|
||||||
|
const { signIn } = useAuthActions();
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size='lg'
|
||||||
|
onClick={() => signIn('authentik')}
|
||||||
|
className='text-lg font-semibold'
|
||||||
|
{...buttonProps}
|
||||||
|
>
|
||||||
|
<div className='flex flex-row my-auto space-x-1'>
|
||||||
|
<Image
|
||||||
|
src={'/icons/misc/gibs-auth-logo.png'}
|
||||||
|
className=''
|
||||||
|
alt="Gib's Auth"
|
||||||
|
width={30}
|
||||||
|
height={30}
|
||||||
|
/>
|
||||||
|
<p>
|
||||||
|
{type === 'signIn' ? 'Sign In' : 'Sign Up'} with Gib's Auth
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
2
apps/next/src/components/layout/auth/buttons/index.tsx
Normal file
2
apps/next/src/components/layout/auth/buttons/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { GibsAuthSignInButton } from './gibs-auth';
|
||||||
|
export { MicrosoftSignInButton } from './microsoft';
|
41
apps/next/src/components/layout/auth/buttons/microsoft.tsx
Normal file
41
apps/next/src/components/layout/auth/buttons/microsoft.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { useAuthActions } from '@convex-dev/auth/react';
|
||||||
|
import { Button, type buttonVariants } from '@/components/ui';
|
||||||
|
import { type ComponentProps } from 'react';
|
||||||
|
import { type VariantProps } from 'class-variance-authority';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
buttonProps?: Omit<ComponentProps<'button'>, 'onClick'> &
|
||||||
|
VariantProps<typeof buttonVariants> & {
|
||||||
|
asChild?: boolean;
|
||||||
|
},
|
||||||
|
type?: 'signIn' | 'signUp';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MicrosoftSignInButton = ({
|
||||||
|
buttonProps,
|
||||||
|
type = 'signIn',
|
||||||
|
}: Props) => {
|
||||||
|
const { signIn } = useAuthActions();
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size='lg'
|
||||||
|
onClick={() => signIn('microsoft-entra-id')}
|
||||||
|
className='text-lg font-semibold mx-auto'
|
||||||
|
{...buttonProps}
|
||||||
|
>
|
||||||
|
<div className='flex flex-row my-auto space-x-2'>
|
||||||
|
<div className='flex flex-row my-auto'>
|
||||||
|
<svg className='scale-150' viewBox='0 0 23 23'>
|
||||||
|
<path fill='#f35325' d='M1 1h10v10H1z'/>
|
||||||
|
<path fill='#81bc06' d='M12 1h10v10H12z'/>
|
||||||
|
<path fill='#05a6f0' d='M1 12h10v10H1z'/>
|
||||||
|
<path fill='#ffba08' d='M12 12h10v10H12z'/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{type === 'signIn' ? 'Sign In' : 'Sign Up'} with Microsoft
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
10
packages/backend/convex/_generated/api.d.ts
vendored
10
packages/backend/convex/_generated/api.d.ts
vendored
@@ -13,9 +13,12 @@ import type {
|
|||||||
FilterApi,
|
FilterApi,
|
||||||
FunctionReference,
|
FunctionReference,
|
||||||
} from "convex/server";
|
} from "convex/server";
|
||||||
import type * as CustomPassword from "../CustomPassword.js";
|
|
||||||
import type * as auth from "../auth.js";
|
import type * as auth from "../auth.js";
|
||||||
import type * as crons from "../crons.js";
|
import type * as crons from "../crons.js";
|
||||||
|
import type * as custom_auth_index from "../custom/auth/index.js";
|
||||||
|
import type * as custom_auth_password_validate from "../custom/auth/password/validate.js";
|
||||||
|
import type * as custom_auth_providers_entra from "../custom/auth/providers/entra.js";
|
||||||
|
import type * as custom_auth_providers_password from "../custom/auth/providers/password.js";
|
||||||
import type * as files from "../files.js";
|
import type * as files from "../files.js";
|
||||||
import type * as http from "../http.js";
|
import type * as http from "../http.js";
|
||||||
import type * as statuses from "../statuses.js";
|
import type * as statuses from "../statuses.js";
|
||||||
@@ -29,9 +32,12 @@ import type * as statuses from "../statuses.js";
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
declare const fullApi: ApiFromModules<{
|
declare const fullApi: ApiFromModules<{
|
||||||
CustomPassword: typeof CustomPassword;
|
|
||||||
auth: typeof auth;
|
auth: typeof auth;
|
||||||
crons: typeof crons;
|
crons: typeof crons;
|
||||||
|
"custom/auth/index": typeof custom_auth_index;
|
||||||
|
"custom/auth/password/validate": typeof custom_auth_password_validate;
|
||||||
|
"custom/auth/providers/entra": typeof custom_auth_providers_entra;
|
||||||
|
"custom/auth/providers/password": typeof custom_auth_providers_password;
|
||||||
files: typeof files;
|
files: typeof files;
|
||||||
http: typeof http;
|
http: typeof http;
|
||||||
statuses: typeof statuses;
|
statuses: typeof statuses;
|
||||||
|
@@ -1,6 +1,3 @@
|
|||||||
import Authentik from '@auth/core/providers/authentik';
|
|
||||||
import MicrosoftEntraID from '@auth/core/providers/microsoft-entra-id';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
|
@@ -8,13 +8,14 @@ import {
|
|||||||
import { api } from './_generated/api';
|
import { api } from './_generated/api';
|
||||||
import { type Id } from './_generated/dataModel';
|
import { type Id } from './_generated/dataModel';
|
||||||
import { action, mutation, query } from './_generated/server';
|
import { action, mutation, query } from './_generated/server';
|
||||||
import Password from './CustomPassword';
|
|
||||||
import Authentik from '@auth/core/providers/authentik';
|
import Authentik from '@auth/core/providers/authentik';
|
||||||
|
import { Entra, Password, validatePassword, } from './custom/auth';
|
||||||
|
|
||||||
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
|
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
|
||||||
providers: [
|
providers: [
|
||||||
Password,
|
Password,
|
||||||
Authentik,
|
Authentik,
|
||||||
|
Entra,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -42,6 +43,20 @@ export const getUser = query(async (ctx) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add this temporarily to packages/backend/convex/auth.ts
|
||||||
|
export const debugMicrosoftConfig = action({
|
||||||
|
args: {},
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
console.log('Microsoft Entra ID Config Debug:', {
|
||||||
|
issuer: process.env.AUTH_MICROSOFT_ENTRA_ID_ISSUER,
|
||||||
|
clientId: process.env.AUTH_MICROSOFT_ENTRA_ID_ID,
|
||||||
|
hasSecret: !!process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET,
|
||||||
|
secretLength: process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET?.length,
|
||||||
|
});
|
||||||
|
return { logged: true };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const getAllUsers = query(async (ctx) => {
|
export const getAllUsers = query(async (ctx) => {
|
||||||
const users = await ctx.db.query('users').collect();
|
const users = await ctx.db.query('users').collect();
|
||||||
return users.map((u) => ({
|
return users.map((u) => ({
|
||||||
@@ -133,19 +148,6 @@ export const updateUserAutomaticLunch = mutation({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const validatePassword = (password: string): boolean => {
|
|
||||||
if (
|
|
||||||
password.length < 8 ||
|
|
||||||
password.length > 100 ||
|
|
||||||
!/\d/.test(password) ||
|
|
||||||
!/[a-z]/.test(password) ||
|
|
||||||
!/[A-Z]/.test(password)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateUserPassword = action({
|
export const updateUserPassword = action({
|
||||||
args: {
|
args: {
|
||||||
currentPassword: v.string(),
|
currentPassword: v.string(),
|
||||||
|
3
packages/backend/convex/custom/auth/index.ts
Normal file
3
packages/backend/convex/custom/auth/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { validatePassword } from './password/validate';
|
||||||
|
export { Entra } from './providers/entra';
|
||||||
|
export { Password } from './providers/password';
|
12
packages/backend/convex/custom/auth/password/validate.ts
Normal file
12
packages/backend/convex/custom/auth/password/validate.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export const validatePassword = (password: string): boolean => {
|
||||||
|
if (
|
||||||
|
password.length < 8 ||
|
||||||
|
password.length > 100 ||
|
||||||
|
!/\d/.test(password) ||
|
||||||
|
!/[a-z]/.test(password) ||
|
||||||
|
!/[A-Z]/.test(password)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
29
packages/backend/convex/custom/auth/providers/entra.ts
Normal file
29
packages/backend/convex/custom/auth/providers/entra.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { type AuthProviderMaterializedConfig } from '@convex-dev/auth/server';
|
||||||
|
|
||||||
|
export const Entra: AuthProviderMaterializedConfig = {
|
||||||
|
id: 'microsoft-entra-id',
|
||||||
|
name: 'Microsoft Entra ID',
|
||||||
|
type: 'oauth',
|
||||||
|
issuer: process.env.AUTH_MICROSOFT_ENTRA_ID_ISSUER!,
|
||||||
|
client: {
|
||||||
|
id: process.env.AUTH_MICROSOFT_ENTRA_ID_ID!,
|
||||||
|
secret: process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET!,
|
||||||
|
},
|
||||||
|
authorization: {
|
||||||
|
url: process.env.AUTH_MICROSOFT_ENTRA_ID_AUTH_URL!,
|
||||||
|
params: {
|
||||||
|
scope: 'openid profile email offline_access',
|
||||||
|
response_type: 'code',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
token:
|
||||||
|
'https://login.microsoftonline.com/16200986-86f1-44d2-974c-cfa99352722c/oauth2/v2.0/token',
|
||||||
|
userinfo: 'https://graph.microsoft.com/oidc/userinfo',
|
||||||
|
profile(profile) {
|
||||||
|
return {
|
||||||
|
id: profile.sub,
|
||||||
|
name: profile.name,
|
||||||
|
email: profile.email,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
@@ -1,9 +1,9 @@
|
|||||||
import { ConvexError } from 'convex/values';
|
import { ConvexError } from 'convex/values';
|
||||||
import { Password } from '@convex-dev/auth/providers/Password';
|
import { Password as DefaultPassword } from '@convex-dev/auth/providers/Password';
|
||||||
import { validatePassword } from './auth';
|
import { validatePassword } from '../password/validate';
|
||||||
import type { DataModel } from './_generated/dataModel';
|
import type { DataModel } from '../../../_generated/dataModel';
|
||||||
|
|
||||||
export default Password<DataModel>({
|
export const Password = DefaultPassword<DataModel>({
|
||||||
profile(params, ctx) {
|
profile(params, ctx) {
|
||||||
return {
|
return {
|
||||||
email: params.email as string,
|
email: params.email as string,
|
Reference in New Issue
Block a user