I can't believe it but I have sign in with Microsoft working!

This commit is contained in:
2025-09-19 16:13:38 -05:00
parent e7cdf7f754
commit fd2999e9bb
11 changed files with 162 additions and 61 deletions

View File

@@ -3,13 +3,11 @@ import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import Link from 'next/link';
import Image from 'next/image';
import { useAuthActions } from '@convex-dev/auth/react';
import { useRouter } from 'next/navigation';
import { ConvexError } from 'convex/values';
import { useState } from 'react';
import {
Button,
Card,
CardContent,
Form,
@@ -27,6 +25,10 @@ import {
TabsTrigger,
} from '@/components/ui';
import { toast } from 'sonner';
import {
GibsAuthSignInButton,
MicrosoftSignInButton
} from '@/components/layout/auth/buttons';
import { PASSWORD_MIN, PASSWORD_MAX, PASSWORD_REGEX } from '@/lib/types';
const signInFormSchema = z.object({
@@ -228,24 +230,7 @@ const SignIn = () => {
</div>
</div>
<div className='flex my-auto justify-center'>
<Button
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&apos;s Auth'
width={30}
height={30}
/>
<p className='my-auto'>
Sign In with Gib&apos;s Auth
</p>
</div>
</Button>
<MicrosoftSignInButton />
</div>
</CardContent>
</Card>
@@ -353,24 +338,7 @@ const SignIn = () => {
</div>
</div>
<div className='flex my-auto justify-center'>
<Button
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&apos;s Auth'
width={30}
height={30}
/>
<p className='my-auto'>
Sign Up with Gib&apos;s Auth
</p>
</div>
</Button>
<MicrosoftSignInButton type='signUp' />
</div>
</CardContent>
</Card>

View 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&apos;s Auth
</p>
</div>
</Button>
);
};

View File

@@ -0,0 +1,2 @@
export { GibsAuthSignInButton } from './gibs-auth';
export { MicrosoftSignInButton } from './microsoft';

View 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>
);
};

View File

@@ -13,9 +13,12 @@ import type {
FilterApi,
FunctionReference,
} from "convex/server";
import type * as CustomPassword from "../CustomPassword.js";
import type * as auth from "../auth.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 http from "../http.js";
import type * as statuses from "../statuses.js";
@@ -29,9 +32,12 @@ import type * as statuses from "../statuses.js";
* ```
*/
declare const fullApi: ApiFromModules<{
CustomPassword: typeof CustomPassword;
auth: typeof auth;
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;
http: typeof http;
statuses: typeof statuses;

View File

@@ -1,6 +1,3 @@
import Authentik from '@auth/core/providers/authentik';
import MicrosoftEntraID from '@auth/core/providers/microsoft-entra-id';
export default {
providers: [
{

View File

@@ -8,13 +8,14 @@ import {
import { api } from './_generated/api';
import { type Id } from './_generated/dataModel';
import { action, mutation, query } from './_generated/server';
import Password from './CustomPassword';
import Authentik from '@auth/core/providers/authentik';
import { Entra, Password, validatePassword, } from './custom/auth';
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
providers: [
Password,
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) => {
const users = await ctx.db.query('users').collect();
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({
args: {
currentPassword: v.string(),

View File

@@ -0,0 +1,3 @@
export { validatePassword } from './password/validate';
export { Entra } from './providers/entra';
export { Password } from './providers/password';

View 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;
};

View 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,
};
},
};

View File

@@ -1,9 +1,9 @@
import { ConvexError } from 'convex/values';
import { Password } from '@convex-dev/auth/providers/Password';
import { validatePassword } from './auth';
import type { DataModel } from './_generated/dataModel';
import { Password as DefaultPassword } from '@convex-dev/auth/providers/Password';
import { validatePassword } from '../password/validate';
import type { DataModel } from '../../../_generated/dataModel';
export default Password<DataModel>({
export const Password = DefaultPassword<DataModel>({
profile(params, ctx) {
return {
email: params.email as string,