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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/deploy-app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ jobs:
- name: Build Frontend Application
run: npm run build
working-directory: ./frontend/app
env:
REACT_APP_FUNCTION_APP_URL: https://${{ env.ENVIRONMENT }}-alpinebot-func.azurewebsites.net

- name: Deploy to Azure Web App
uses: azure/webapps-deploy@v2
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/deploy-function.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ jobs:
with:
app-name: ${{ env.ENVIRONMENT }}-alpinebot-func
package: ./backend
scm-do-build-during-deployment: 'true'
enable-oryx-build: 'true'

- name: Output deployment information
run: |
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ jobs:
with:
app-name: ${{ env.ENVIRONMENT }}-alpinebot-func
package: ./backend
scm-do-build-during-deployment: 'true'
enable-oryx-build: 'true'

- name: Output deployment information
run: |
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/destroy-infra.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
echo "TF_VAR_az_subscription_id=${{ secrets.AZURE_SUBSCRIPTION_ID }}" >> $GITHUB_ENV
echo "TF_VAR_az_container_name=${{ env.ENVIRONMENT }}-bkd-alpinebot-co" >> $GITHUB_ENV
echo "TF_VAR_sp_object_id=${{ secrets.AZURE_SP_OBJECT_ID }}" >> $GITHUB_ENV
echo "TF_VAR_az_openai_key_value=${{ secrets.AZURE_OPENAI_KEY }}" >> $GITHUB_ENV
echo "TF_VAR_postgresql_admin_password=${{ secrets.POSTGRESQL_ADMIN_PASSWORD }}" >> $GITHUB_ENV
echo "TF_VAR_postgresql_admin_username=${{ secrets.POSTGRESQL_ADMIN_USERNAME }}" >> $GITHUB_ENV
echo "TF_VAR_google_client_id=${{ secrets.GOOGLE_CLIENT_ID }}" >> $GITHUB_ENV
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/destroy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ jobs:
echo "TF_VAR_az_subscription_id=${{ secrets.AZURE_SUBSCRIPTION_ID }}" >> $GITHUB_ENV
echo "TF_VAR_az_container_name=${{ env.ENVIRONMENT }}-bkd-alpinebot-co" >> $GITHUB_ENV
echo "TF_VAR_sp_object_id=${{ secrets.AZURE_SP_OBJECT_ID }}" >> $GITHUB_ENV
echo "TF_VAR_az_openai_key_value=${{ secrets.AZURE_OPENAI_KEY }}" >> $GITHUB_ENV
echo "TF_VAR_postgresql_admin_password=${{ secrets.POSTGRESQL_ADMIN_PASSWORD }}" >> $GITHUB_ENV
echo "TF_VAR_postgresql_admin_username=${{ secrets.POSTGRESQL_ADMIN_USERNAME }}" >> $GITHUB_ENV
echo "TF_VAR_google_client_id=${{ secrets.GOOGLE_CLIENT_ID }}" >> $GITHUB_ENV
Expand Down
2 changes: 1 addition & 1 deletion backend/function_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def get_openai_client():
azure_endpoint=api_base
)

@app.route(route="chat", methods=["POST"], auth_level=func.AuthLevel.FUNCTION)
@app.route(route="chat", methods=["POST"], auth_level=func.AuthLevel.ANONYMOUS)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Changing the authentication level to ANONYMOUS exposes this function endpoint to the public internet without any authentication. This means anyone can call your chat API, which could lead to significant security risks and unexpected costs from the underlying Azure OpenAI service. The function should be secured. Consider using Azure App Service's built-in authentication (Easy Auth) on the Function App as well, or revert to AuthLevel.FUNCTION and securely provide the function key to the frontend application via its application settings.

def chat(req: func.HttpRequest) -> func.HttpResponse:
"""
HTTP trigger function for chatbot queries.
Expand Down
4 changes: 4 additions & 0 deletions frontend/app/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Azure Function App URL for chat API
# Format: https://<environment>-alpinebot-func.azurewebsites.net
# Example for dev: https://dev-alpinebot-func.azurewebsites.net
REACT_APP_FUNCTION_APP_URL=
14 changes: 14 additions & 0 deletions frontend/app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,20 @@ npm start

**Note**: When running locally, the Azure App Service authentication endpoints (`/.auth/me`, `/.auth/login/*`, `/.auth/logout`) will not work unless you configure a local development proxy or mock these endpoints.

### Environment Variables

The application requires the following environment variable to connect to the backend API:

- `REACT_APP_FUNCTION_APP_URL`: The URL of the Azure Function App (e.g., `https://dev-alpinebot-func.azurewebsites.net`)

For local development, create a `.env.local` file (see `.env.example` for reference):

```bash
REACT_APP_FUNCTION_APP_URL=https://dev-alpinebot-func.azurewebsites.net
```

This variable is automatically set during CI/CD deployment based on the environment.

### Building for Production

```bash
Expand Down
52 changes: 47 additions & 5 deletions frontend/app/src/HomePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const HomePage = ({ user, onLogout }) => {
scrollToBottom();
}, [messages]);

const handleSend = (e) => {
const handleSend = async (e) => {
e.preventDefault();
if (!inputValue.trim()) return;

Expand All @@ -43,20 +43,62 @@ const HomePage = ({ user, onLogout }) => {
};

setMessages((prev) => [...prev, userMessage]);
const currentMessage = inputValue;
setInputValue("");
setIsTyping(true);

// Simulate bot response (placeholder for future API integration)
setTimeout(() => {
try {
// Get Function App URL from environment variable
const functionAppUrl = process.env.REACT_APP_FUNCTION_APP_URL || "";

if (!functionAppUrl) {
throw new Error("Function App URL not configured");
}

// Build conversation history for API
const conversationHistory = messages.map((msg) => ({
role: msg.type === "user" ? "user" : "assistant",
content: msg.text,
}));

// Call Azure Function App API
const response = await fetch(`${functionAppUrl}/api/chat`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
message: currentMessage,
conversation_history: conversationHistory,
}),
});

if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}

const data = await response.json();

const botMessage = {
id: Date.now() + 1,
type: "bot",
text: "I'm a placeholder response. Integration with Azure OpenAI will be implemented in future tasks.",
text: data.response || "I apologize, but I couldn't generate a response.",
timestamp: new Date(),
};
setMessages((prev) => [...prev, botMessage]);
} catch (error) {
// Log error without sensitive details
console.error("Error calling chat API");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The catch block logs a generic error message but doesn't include the actual error object. This makes debugging difficult as the specific reason for the failure (e.g., network error, API error status) is lost. While the comment mentions avoiding sensitive details, the error from fetch or a thrown Error is generally safe to log in development environments and is very useful for debugging.

Suggested change
console.error("Error calling chat API");
console.error("Error calling chat API:", error);

const errorMessage = {
id: Date.now() + 1,
type: "bot",
text: "I apologize, but I'm having trouble connecting to the service. Please try again later.",
timestamp: new Date(),
};
setMessages((prev) => [...prev, errorMessage]);
Comment on lines 82 to +98

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using Date.now() or Date.now() + 1 (lines 83 and 93) to generate unique ids for React components is not a robust approach. It's possible for multiple messages to be created within the same millisecond, which would lead to duplicate keys and cause rendering issues. This also applies to the user message ID created on line 39. A better approach is to use a more reliable source of unique identifiers, such as crypto.randomUUID() or a simple counter stored in a useRef.

} finally {
setIsTyping(false);
}, 1500);
}
};

const handleVote = (messageId, vote) => {
Expand Down
2 changes: 2 additions & 0 deletions modules/function_app/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ resource "azurerm_linux_function_app" "function_app" {
"FUNCTIONS_WORKER_RUNTIME" = "python"
"AzureWebJobsFeatureFlags" = "EnableWorkerIndexing"
"APPLICATIONINSIGHTS_CONNECTION_STRING" = var.app_insights_connection_string
"SCM_DO_BUILD_DURING_DEPLOYMENT" = "true"
"ENABLE_ORYX_BUILD" = "true"
},
var.app_settings
)
Expand Down