Skip to content
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ jobs:
env:
NODE_OPTIONS: '--no-warnings'
NEXT_PUBLIC_APP_URL: 'https://www.sim.ai'
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/simstudio'
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
run: bun run test

- name: Build application
env:
NODE_OPTIONS: '--no-warnings'
NEXT_PUBLIC_APP_URL: 'https://www.sim.ai'
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/simstudio'
STRIPE_SECRET_KEY: 'dummy_key_for_ci_only'
STRIPE_WEBHOOK_SECRET: 'dummy_secret_for_ci_only'
RESEND_API_KEY: 'dummy_key_for_ci_only'
Expand Down Expand Up @@ -71,7 +73,7 @@ jobs:
run: bun install

- name: Apply migrations
working-directory: ./apps/sim
working-directory: ./packages/db
env:
DATABASE_URL: ${{ github.ref == 'refs/heads/main' && secrets.DATABASE_URL || secrets.STAGING_DATABASE_URL }}
run: bunx drizzle-kit migrate
run: bunx drizzle-kit migrate --config=./drizzle.config.ts
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,11 @@ Update your `.env` file with the database URL:
DATABASE_URL="postgresql://postgres:your_password@localhost:5432/simstudio"
```

4. Set up the database:
4. Set up the database (from packages/db):

```bash
bunx drizzle-kit migrate
cd packages/db
bunx drizzle-kit migrate --config=./drizzle.config.ts
```

5. Start the development servers:
Expand Down
63 changes: 61 additions & 2 deletions apps/docs/content/docs/en/tools/sharepoint.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Sharepoint
description: Read and create pages
description: Work with pages and lists
---

import { BlockInfoCard } from "@/components/ui/block-info-card"
Expand Down Expand Up @@ -61,7 +61,7 @@ In Sim, the SharePoint integration empowers your agents to create and access Sha

## Usage Instructions

Integrate Sharepoint into the workflow. Can read and create pages, and list sites. Requires OAuth.
Integrate SharePoint into the workflow. Read/create pages, list sites, and work with lists (read, create, update items). Requires OAuth.



Expand Down Expand Up @@ -124,6 +124,65 @@ List details of all SharePoint sites
| --------- | ---- | ----------- |
| `site` | object | Information about the current SharePoint site |

### `sharepoint_create_list`

Create a new list in a SharePoint site

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | No | The ID of the SharePoint site \(internal use\) |
| `siteSelector` | string | No | Select the SharePoint site |
| `listDisplayName` | string | Yes | Display name of the list to create |
| `listDescription` | string | No | Description of the list |
| `listTemplate` | string | No | List template name \(e.g., 'genericList'\) |
| `pageContent` | string | No | Optional JSON of columns. Either a top-level array of column definitions or an object with \{ columns: \[...\] \}. |

#### Output

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `list` | object | Created SharePoint list information |

### `sharepoint_get_list`

Get metadata (and optionally columns/items) for a SharePoint list

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteSelector` | string | No | Select the SharePoint site |
| `siteId` | string | No | The ID of the SharePoint site \(internal use\) |
| `listId` | string | No | The ID of the list to retrieve |

#### Output

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `list` | object | Information about the SharePoint list |

### `sharepoint_update_list`

Update the properties (fields) on a SharePoint list item

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteSelector` | string | No | Select the SharePoint site |
| `siteId` | string | No | The ID of the SharePoint site \(internal use\) |
| `listId` | string | No | The ID of the list containing the item |
| `itemId` | string | Yes | The ID of the list item to update |
| `listItemFields` | object | Yes | Field values to update on the list item |

#### Output

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `item` | object | Updated SharePoint list item |



## Notes
Expand Down
14 changes: 2 additions & 12 deletions apps/sim/app/(auth)/components/oauth-provider-checker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,9 @@ import { env } from '@/lib/env'
import { isProd } from '@/lib/environment'

export async function getOAuthProviderStatus() {
const githubAvailable = !!(
env.GITHUB_CLIENT_ID &&
env.GITHUB_CLIENT_SECRET &&
env.GITHUB_CLIENT_ID !== 'placeholder' &&
env.GITHUB_CLIENT_SECRET !== 'placeholder'
)
const githubAvailable = !!(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET)

const googleAvailable = !!(
env.GOOGLE_CLIENT_ID &&
env.GOOGLE_CLIENT_SECRET &&
env.GOOGLE_CLIENT_ID !== 'placeholder' &&
env.GOOGLE_CLIENT_SECRET !== 'placeholder'
)
const googleAvailable = !!(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET)

return { githubAvailable, googleAvailable, isProduction: isProd }
}
13 changes: 0 additions & 13 deletions apps/sim/app/(auth)/components/social-login-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,6 @@ export function SocialLoginButtons({
setIsGithubLoading(true)
try {
await client.signIn.social({ provider: 'github', callbackURL })

// Mark that the user has previously logged in
if (typeof window !== 'undefined') {
localStorage.setItem('has_logged_in_before', 'true')
document.cookie = 'has_logged_in_before=true; path=/; max-age=31536000; SameSite=Lax' // 1 year expiry
}
} catch (err: any) {
let errorMessage = 'Failed to sign in with GitHub'

Expand All @@ -66,13 +60,6 @@ export function SocialLoginButtons({
setIsGoogleLoading(true)
try {
await client.signIn.social({ provider: 'google', callbackURL })

// Mark that the user has previously logged in
if (typeof window !== 'undefined') {
localStorage.setItem('has_logged_in_before', 'true')
// Also set a cookie to enable middleware to check login status
document.cookie = 'has_logged_in_before=true; path=/; max-age=31536000; SameSite=Lax' // 1 year expiry
}
} catch (err: any) {
let errorMessage = 'Failed to sign in with Google'

Expand Down
51 changes: 8 additions & 43 deletions apps/sim/app/(auth)/login/login-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ const validatePassword = (passwordValue: string): string[] => {

if (!PASSWORD_VALIDATIONS.required.test(passwordValue)) {
errors.push(PASSWORD_VALIDATIONS.required.message)
return errors // Return early for required field
return errors
}

if (!PASSWORD_VALIDATIONS.notEmpty.test(passwordValue)) {
errors.push(PASSWORD_VALIDATIONS.notEmpty.message)
return errors // Return early for empty field
return errors
}

return errors
Expand All @@ -104,11 +104,9 @@ export default function LoginPage({
const [showValidationError, setShowValidationError] = useState(false)
const [buttonClass, setButtonClass] = useState('auth-button-gradient')

// Initialize state for URL parameters
const [callbackUrl, setCallbackUrl] = useState('/workspace')
const [isInviteFlow, setIsInviteFlow] = useState(false)

// Forgot password states
const [forgotPasswordOpen, setForgotPasswordOpen] = useState(false)
const [forgotPasswordEmail, setForgotPasswordEmail] = useState('')
const [isSubmittingReset, setIsSubmittingReset] = useState(false)
Expand All @@ -117,38 +115,31 @@ export default function LoginPage({
message: string
}>({ type: null, message: '' })

// Email validation state
const [email, setEmail] = useState('')
const [emailErrors, setEmailErrors] = useState<string[]>([])
const [showEmailValidationError, setShowEmailValidationError] = useState(false)

// Extract URL parameters after component mounts to avoid SSR issues
useEffect(() => {
setMounted(true)

// Only access search params on the client side
if (searchParams) {
const callback = searchParams.get('callbackUrl')
if (callback) {
// Validate the callbackUrl before setting it
if (validateCallbackUrl(callback)) {
setCallbackUrl(callback)
} else {
logger.warn('Invalid callback URL detected and blocked:', { url: callback })
// Keep the default safe value ('/workspace')
}
}

const inviteFlow = searchParams.get('invite_flow') === 'true'
setIsInviteFlow(inviteFlow)
}

// Check if CSS variable has been customized
const checkCustomBrand = () => {
const computedStyle = getComputedStyle(document.documentElement)
const brandAccent = computedStyle.getPropertyValue('--brand-accent-hex').trim()

// Check if the CSS variable exists and is different from the default
if (brandAccent && brandAccent !== '#6f3dfa') {
setButtonClass('auth-button-custom')
} else {
Expand All @@ -158,7 +149,6 @@ export default function LoginPage({

checkCustomBrand()

// Also check on window resize or theme changes
window.addEventListener('resize', checkCustomBrand)
const observer = new MutationObserver(checkCustomBrand)
observer.observe(document.documentElement, {
Expand Down Expand Up @@ -189,7 +179,6 @@ export default function LoginPage({
const newEmail = e.target.value
setEmail(newEmail)

// Silently validate but don't show errors until submit
const errors = validateEmailField(newEmail)
setEmailErrors(errors)
setShowEmailValidationError(false)
Expand All @@ -199,7 +188,6 @@ export default function LoginPage({
const newPassword = e.target.value
setPassword(newPassword)

// Silently validate but don't show errors until submit
const errors = validatePassword(newPassword)
setPasswordErrors(errors)
setShowValidationError(false)
Expand All @@ -210,26 +198,23 @@ export default function LoginPage({
setIsLoading(true)

const formData = new FormData(e.currentTarget)
const email = formData.get('email') as string
const emailRaw = formData.get('email') as string
const email = emailRaw.trim().toLowerCase()

// Validate email on submit
const emailValidationErrors = validateEmailField(email)
setEmailErrors(emailValidationErrors)
setShowEmailValidationError(emailValidationErrors.length > 0)

// Validate password on submit
const passwordValidationErrors = validatePassword(password)
setPasswordErrors(passwordValidationErrors)
setShowValidationError(passwordValidationErrors.length > 0)

// If there are validation errors, stop submission
if (emailValidationErrors.length > 0 || passwordValidationErrors.length > 0) {
setIsLoading(false)
return
}

try {
// Final validation before submission
const safeCallbackUrl = validateCallbackUrl(callbackUrl) ? callbackUrl : '/workspace'

const result = await client.signIn.email(
Expand Down Expand Up @@ -291,33 +276,13 @@ export default function LoginPage({
setIsLoading(false)
return
}

// Mark that the user has previously logged in
if (typeof window !== 'undefined') {
localStorage.setItem('has_logged_in_before', 'true')
document.cookie = 'has_logged_in_before=true; path=/; max-age=31536000; SameSite=Lax' // 1 year expiry
}
} catch (err: any) {
// Handle only the special verification case that requires a redirect
if (err.message?.includes('not verified') || err.code?.includes('EMAIL_NOT_VERIFIED')) {
try {
await client.emailOtp.sendVerificationOtp({
email,
type: 'email-verification',
})

if (typeof window !== 'undefined') {
sessionStorage.setItem('verificationEmail', email)
}

router.push('/verify')
return
} catch (_verifyErr) {
setPasswordErrors(['Failed to send verification code. Please try again later.'])
setShowValidationError(true)
setIsLoading(false)
return
if (typeof window !== 'undefined') {
sessionStorage.setItem('verificationEmail', email)
}
router.push('/verify')
return
}

console.error('Uncaught login error:', err)
Expand Down
2 changes: 0 additions & 2 deletions apps/sim/app/(auth)/reset-password/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ function ResetPasswordContent() {
text: '',
})

// Validate token presence
useEffect(() => {
if (!token) {
setStatusMessage({
Expand Down Expand Up @@ -60,7 +59,6 @@ function ResetPasswordContent() {
text: 'Password reset successful! Redirecting to login...',
})

// Redirect to login page after 1.5 seconds
setTimeout(() => {
router.push('/login?resetSuccess=true')
}, 1500)
Expand Down
7 changes: 0 additions & 7 deletions apps/sim/app/(auth)/reset-password/reset-password-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,10 @@ export function RequestResetForm({
const [buttonClass, setButtonClass] = useState('auth-button-gradient')

useEffect(() => {
// Check if CSS variable has been customized
const checkCustomBrand = () => {
const computedStyle = getComputedStyle(document.documentElement)
const brandAccent = computedStyle.getPropertyValue('--brand-accent-hex').trim()

// Check if the CSS variable exists and is different from the default
if (brandAccent && brandAccent !== '#6f3dfa') {
setButtonClass('auth-button-custom')
} else {
Expand All @@ -45,7 +43,6 @@ export function RequestResetForm({

checkCustomBrand()

// Also check on window resize or theme changes
window.addEventListener('resize', checkCustomBrand)
const observer = new MutationObserver(checkCustomBrand)
observer.observe(document.documentElement, {
Expand Down Expand Up @@ -132,12 +129,10 @@ export function SetNewPasswordForm({
const [buttonClass, setButtonClass] = useState('auth-button-gradient')

useEffect(() => {
// Check if CSS variable has been customized
const checkCustomBrand = () => {
const computedStyle = getComputedStyle(document.documentElement)
const brandAccent = computedStyle.getPropertyValue('--brand-accent-hex').trim()

// Check if the CSS variable exists and is different from the default
if (brandAccent && brandAccent !== '#6f3dfa') {
setButtonClass('auth-button-custom')
} else {
Expand All @@ -147,7 +142,6 @@ export function SetNewPasswordForm({

checkCustomBrand()

// Also check on window resize or theme changes
window.addEventListener('resize', checkCustomBrand)
const observer = new MutationObserver(checkCustomBrand)
observer.observe(document.documentElement, {
Expand All @@ -164,7 +158,6 @@ export function SetNewPasswordForm({
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()

// Simple validation
if (password.length < 8) {
setValidationMessage('Password must be at least 8 characters long')
return
Expand Down
Loading
Loading