mirror of
https://github.com/wekan/wekan.git
synced 2026-02-17 21:48:07 +01:00
Merge pull request #11 from omriza5/feature/deploy-github-workflow
Remove outdated API and UI testing workflows
This commit is contained in:
commit
00ca9772c4
8 changed files with 312 additions and 120 deletions
200
.github/workflows/e2e-testing.yml
vendored
Normal file
200
.github/workflows/e2e-testing.yml
vendored
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
name: Deploy testing environment to EC2
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
wekan_image_tag: ${{ steps.docker_image_build.outputs.tag }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository(omriza5/wekan)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build and push docker image
|
||||||
|
id: docker_image_build
|
||||||
|
run: |
|
||||||
|
# Login to DockerHub
|
||||||
|
echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
|
||||||
|
|
||||||
|
# Use short commit SHA (first 7 characters)
|
||||||
|
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||||
|
TAG="${SHORT_SHA}-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/wekan:$TAG .
|
||||||
|
|
||||||
|
docker push ${{ secrets.DOCKERHUB_USERNAME }}/wekan:$TAG
|
||||||
|
|
||||||
|
# Save the tag for later steps
|
||||||
|
echo "WEKAN_IMAGE_TAG=$TAG" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Create .env file
|
||||||
|
run: |
|
||||||
|
echo "WEKAN_IMAGE=omriza5/wekan:${WEKAN_IMAGE_TAG}" >> .env
|
||||||
|
|
||||||
|
- name: Copy .env file to EC2
|
||||||
|
uses: appleboy/scp-action@v0.1.7
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.WEKAN_EC2_HOST_IP }}
|
||||||
|
username: ubuntu
|
||||||
|
key: ${{ secrets.EC2_SSH_KEY }}
|
||||||
|
source: ".env"
|
||||||
|
target: "/home/ubuntu/"
|
||||||
|
|
||||||
|
- name: Copy docker-compose file to EC2
|
||||||
|
uses: appleboy/scp-action@v0.1.7
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.WEKAN_EC2_HOST_IP }}
|
||||||
|
username: ubuntu
|
||||||
|
key: ${{ secrets.EC2_SSH_KEY }}
|
||||||
|
source: "docker-compose.yml"
|
||||||
|
target: "/home/ubuntu/"
|
||||||
|
|
||||||
|
- name: Deploy to EC2
|
||||||
|
uses: appleboy/ssh-action@v1.0.3
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.WEKAN_EC2_HOST_IP }}
|
||||||
|
username: ubuntu
|
||||||
|
key: ${{ secrets.EC2_SSH_KEY }}
|
||||||
|
script: |
|
||||||
|
# Stop and remove containers with volumes
|
||||||
|
sudo docker compose down -v || true
|
||||||
|
|
||||||
|
# Clean up everything including named volumes
|
||||||
|
sudo docker volume rm $(sudo docker volume ls -q) 2>/dev/null || true
|
||||||
|
|
||||||
|
sudo docker stop $(sudo docker ps -aq) 2>/dev/null || true
|
||||||
|
sudo docker rm $(sudo docker ps -aq) 2>/dev/null || true
|
||||||
|
|
||||||
|
# Remove all images to free space
|
||||||
|
sudo docker rmi $(sudo docker images -q) 2>/dev/null || true
|
||||||
|
|
||||||
|
# Clean up networks (volumes already removed above)
|
||||||
|
sudo docker network prune -f || true
|
||||||
|
|
||||||
|
echo "${{ secrets.DOCKERHUB_PASSWORD }}" | sudo docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
|
||||||
|
|
||||||
|
sudo docker compose pull
|
||||||
|
sudo docker compose up -d
|
||||||
|
|
||||||
|
API-tests:
|
||||||
|
needs: deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python 3.12
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Create test user via Database
|
||||||
|
uses: appleboy/ssh-action@v1.0.3
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.WEKAN_EC2_HOST_IP }}
|
||||||
|
username: ubuntu
|
||||||
|
key: ${{ secrets.EC2_SSH_KEY }}
|
||||||
|
script: |
|
||||||
|
# Wait for Wekan to be fully ready
|
||||||
|
echo "Waiting for Wekan to start..."
|
||||||
|
for i in {1..24}; do
|
||||||
|
if curl -s http://localhost > /dev/null 2>&1; then
|
||||||
|
echo "Wekan is responding!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Waiting... (attempt $i/24)"
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
# Create user directly in database with the exact structure from browser
|
||||||
|
echo "Creating test user directly in database..."
|
||||||
|
sudo docker exec wekan-db mongosh wekan --eval '
|
||||||
|
// Remove existing user first
|
||||||
|
db.users.deleteMany({username: "omriza5"});
|
||||||
|
|
||||||
|
// Create user with exact structure from browser
|
||||||
|
const result = db.users.insertOne({
|
||||||
|
_id: "omriza5_" + new Date().getTime(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
services: {
|
||||||
|
password: {
|
||||||
|
bcrypt: "$2b$10$v9266B4sMuTCOgPsnIPibuxKoUwELIqPvTn7GQqGvvVibAEsmphsm"
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
verificationTokens: [
|
||||||
|
{
|
||||||
|
token: "token_" + Math.random().toString(36).substring(2),
|
||||||
|
address: "omriza5@gmail.com",
|
||||||
|
when: new Date()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
username: "omriza5",
|
||||||
|
emails: [{ address: "omriza5@gmail.com", verified: false }],
|
||||||
|
isAdmin: true,
|
||||||
|
modifiedAt: new Date(),
|
||||||
|
profile: {
|
||||||
|
boardView: "board-view-swimlanes",
|
||||||
|
listSortBy: "-modifiedAt",
|
||||||
|
templatesBoardId: "",
|
||||||
|
cardTemplatesSwimlaneId: "",
|
||||||
|
listTemplatesSwimlaneId: "",
|
||||||
|
boardTemplatesSwimlaneId: "",
|
||||||
|
listWidths: {},
|
||||||
|
listConstraints: {},
|
||||||
|
autoWidthBoards: {},
|
||||||
|
swimlaneHeights: {},
|
||||||
|
keyboardShortcuts: false,
|
||||||
|
verticalScrollbars: true,
|
||||||
|
showWeekOfYear: true
|
||||||
|
},
|
||||||
|
authenticationMethod: "password",
|
||||||
|
sessionData: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.acknowledged) {
|
||||||
|
print("User omriza5 created successfully");
|
||||||
|
} else {
|
||||||
|
print("Failed to create user");
|
||||||
|
}
|
||||||
|
' || echo "Failed to execute MongoDB command"
|
||||||
|
|
||||||
|
# Verify user was created
|
||||||
|
echo "Verifying user creation..."
|
||||||
|
sudo docker exec wekan-db mongosh wekan --eval 'db.users.findOne({username: "omriza5"}, {username: 1, emails: 1, isAdmin: 1})' || echo "User verification failed"
|
||||||
|
|
||||||
|
# Verify login works
|
||||||
|
echo "Testing login..."
|
||||||
|
LOGIN_RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" \
|
||||||
|
-H "Content-type:application/json" \
|
||||||
|
-X POST http://localhost/users/login \
|
||||||
|
-d '{"username":"omriza5","password":"123456"}')
|
||||||
|
|
||||||
|
LOGIN_CODE=$(echo $LOGIN_RESPONSE | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
|
||||||
|
if [[ "$LOGIN_CODE" == "200" ]]; then
|
||||||
|
echo "Login test successful"
|
||||||
|
else
|
||||||
|
echo "Login test failed (Code: $LOGIN_CODE)"
|
||||||
|
echo "Response: $(echo $LOGIN_RESPONSE | sed -e 's/HTTPSTATUS:.*//g')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run API tests
|
||||||
|
env:
|
||||||
|
BASE_URL: ${{ secrets.WEKAN_URL }}
|
||||||
|
run: |
|
||||||
|
pytest --maxfail=5 --disable-warnings -v
|
||||||
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -58,3 +58,4 @@ pip-delete-this-directory.txt
|
||||||
.coverage
|
.coverage
|
||||||
htmlcov/
|
htmlcov/
|
||||||
*.pem
|
*.pem
|
||||||
|
*.env
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,15 @@ services:
|
||||||
- wekan-tier
|
- wekan-tier
|
||||||
expose:
|
expose:
|
||||||
- 27017
|
- 27017
|
||||||
|
ports:
|
||||||
|
- 27017:27017
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- wekan-db:/data/db
|
- wekan-db:/data/db
|
||||||
- wekan-db-dump:/dump
|
- wekan-db-dump:/dump
|
||||||
|
|
||||||
wekan:
|
wekan:
|
||||||
image: ghcr.io/wekan/wekan:latest
|
image: ${WEKAN_IMAGE}
|
||||||
container_name: wekan-app
|
container_name: wekan-app
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
|
|
@ -33,6 +35,9 @@ services:
|
||||||
- BIGEVENTS_PATTERN=NONE
|
- BIGEVENTS_PATTERN=NONE
|
||||||
- BROWSER_POLICY_ENABLED=true
|
- BROWSER_POLICY_ENABLED=true
|
||||||
- LDAP_BACKGROUND_SYNC_INTERVAL=''
|
- LDAP_BACKGROUND_SYNC_INTERVAL=''
|
||||||
|
- ACCOUNTS_LOCKOUT_UNKNOWN_USERS=false
|
||||||
|
- ACCOUNTS_REGISTRATION_VERIFY_EMAIL=false
|
||||||
|
- DISABLE_REGISTRATION=false
|
||||||
depends_on:
|
depends_on:
|
||||||
- wekandb
|
- wekandb
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
||||||
194
models/users.js
194
models/users.js
|
|
@ -546,9 +546,9 @@ Users.attachSchema(
|
||||||
|
|
||||||
Users.allow({
|
Users.allow({
|
||||||
update(userId, doc) {
|
update(userId, doc) {
|
||||||
const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser();
|
const user =
|
||||||
if (user?.isAdmin)
|
ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser();
|
||||||
return true;
|
if (user?.isAdmin) return true;
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -583,12 +583,14 @@ Users.allow({
|
||||||
// Non-Admin users can not change to Admin
|
// Non-Admin users can not change to Admin
|
||||||
Users.deny({
|
Users.deny({
|
||||||
update(userId, board, fieldNames) {
|
update(userId, board, fieldNames) {
|
||||||
return _.contains(fieldNames, 'isAdmin') && !ReactiveCache.getCurrentUser().isAdmin;
|
return (
|
||||||
|
_.contains(fieldNames, 'isAdmin') &&
|
||||||
|
!ReactiveCache.getCurrentUser().isAdmin
|
||||||
|
);
|
||||||
},
|
},
|
||||||
fetch: [],
|
fetch: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Search a user in the complete server database by its name, username or emails adress. This
|
// Search a user in the complete server database by its name, username or emails adress. This
|
||||||
// is used for instance to add a new user to a board.
|
// is used for instance to add a new user to a board.
|
||||||
UserSearchIndex = new Index({
|
UserSearchIndex = new Index({
|
||||||
|
|
@ -714,7 +716,7 @@ Users.helpers({
|
||||||
orgIdsUserBelongs() {
|
orgIdsUserBelongs() {
|
||||||
let ret = '';
|
let ret = '';
|
||||||
if (this.orgs) {
|
if (this.orgs) {
|
||||||
ret = this.orgs.map(org => org.orgId).join(',');
|
ret = this.orgs.map((org) => org.orgId).join(',');
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
@ -732,7 +734,7 @@ Users.helpers({
|
||||||
teamIdsUserBelongs() {
|
teamIdsUserBelongs() {
|
||||||
let ret = '';
|
let ret = '';
|
||||||
if (this.teams) {
|
if (this.teams) {
|
||||||
ret = this.teams.map(team => team.teamId).join(',');
|
ret = this.teams.map((team) => team.teamId).join(',');
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
@ -801,7 +803,7 @@ Users.helpers({
|
||||||
},
|
},
|
||||||
|
|
||||||
getListWidths() {
|
getListWidths() {
|
||||||
const { listWidths = {}, } = this.profile || {};
|
const { listWidths = {} } = this.profile || {};
|
||||||
return listWidths;
|
return listWidths;
|
||||||
},
|
},
|
||||||
getListWidth(boardId, listId) {
|
getListWidth(boardId, listId) {
|
||||||
|
|
@ -888,8 +890,13 @@ Users.helpers({
|
||||||
const notification = notifications[index];
|
const notification = notifications[index];
|
||||||
// this preserves their db sort order for editing
|
// this preserves their db sort order for editing
|
||||||
notification.dbIndex = index;
|
notification.dbIndex = index;
|
||||||
if (!notification.activityObj && typeof(notification.activity) === 'string') {
|
if (
|
||||||
notification.activityObj = ReactiveMiniMongoIndex.getActivityWithId(notification.activity);
|
!notification.activityObj &&
|
||||||
|
typeof notification.activity === 'string'
|
||||||
|
) {
|
||||||
|
notification.activityObj = ReactiveMiniMongoIndex.getActivityWithId(
|
||||||
|
notification.activity,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// newest first. don't use reverse() because it changes the array inplace, so sometimes the array is reversed twice and oldest items at top again
|
// newest first. don't use reverse() because it changes the array inplace, so sometimes the array is reversed twice and oldest items at top again
|
||||||
|
|
@ -1360,11 +1367,13 @@ if (Meteor.isServer) {
|
||||||
check(userTeamsArray, Array);
|
check(userTeamsArray, Array);
|
||||||
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
||||||
// Thanks to mc-marcy and xet7 !
|
// Thanks to mc-marcy and xet7 !
|
||||||
if (fullname.includes('/') ||
|
if (
|
||||||
username.includes('/') ||
|
fullname.includes('/') ||
|
||||||
email.includes('/') ||
|
username.includes('/') ||
|
||||||
initials.includes('/')) {
|
email.includes('/') ||
|
||||||
return false;
|
initials.includes('/')
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (ReactiveCache.getCurrentUser()?.isAdmin) {
|
if (ReactiveCache.getCurrentUser()?.isAdmin) {
|
||||||
const nUsersWithUsername = ReactiveCache.getUsers({
|
const nUsersWithUsername = ReactiveCache.getUsers({
|
||||||
|
|
@ -1408,9 +1417,8 @@ if (Meteor.isServer) {
|
||||||
check(userId, String);
|
check(userId, String);
|
||||||
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
||||||
// Thanks to mc-marcy and xet7 !
|
// Thanks to mc-marcy and xet7 !
|
||||||
if (username.includes('/') ||
|
if (username.includes('/') || userId.includes('/')) {
|
||||||
userId.includes('/')) {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
if (ReactiveCache.getCurrentUser()?.isAdmin) {
|
if (ReactiveCache.getCurrentUser()?.isAdmin) {
|
||||||
const nUsersWithUsername = ReactiveCache.getUsers({
|
const nUsersWithUsername = ReactiveCache.getUsers({
|
||||||
|
|
@ -1432,9 +1440,8 @@ if (Meteor.isServer) {
|
||||||
check(username, String);
|
check(username, String);
|
||||||
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
||||||
// Thanks to mc-marcy and xet7 !
|
// Thanks to mc-marcy and xet7 !
|
||||||
if (username.includes('/') ||
|
if (username.includes('/') || email.includes('/')) {
|
||||||
email.includes('/')) {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
if (ReactiveCache.getCurrentUser()?.isAdmin) {
|
if (ReactiveCache.getCurrentUser()?.isAdmin) {
|
||||||
if (Array.isArray(email)) {
|
if (Array.isArray(email)) {
|
||||||
|
|
@ -1472,10 +1479,12 @@ if (Meteor.isServer) {
|
||||||
check(userId, String);
|
check(userId, String);
|
||||||
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
||||||
// Thanks to mc-marcy and xet7 !
|
// Thanks to mc-marcy and xet7 !
|
||||||
if (username.includes('/') ||
|
if (
|
||||||
email.includes('/') ||
|
username.includes('/') ||
|
||||||
userId.includes('/')) {
|
email.includes('/') ||
|
||||||
return false;
|
userId.includes('/')
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (ReactiveCache.getCurrentUser()?.isAdmin) {
|
if (ReactiveCache.getCurrentUser()?.isAdmin) {
|
||||||
if (Array.isArray(email)) {
|
if (Array.isArray(email)) {
|
||||||
|
|
@ -1498,9 +1507,8 @@ if (Meteor.isServer) {
|
||||||
check(userId, String);
|
check(userId, String);
|
||||||
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
||||||
// Thanks to mc-marcy and xet7 !
|
// Thanks to mc-marcy and xet7 !
|
||||||
if (email.includes('/') ||
|
if (email.includes('/') || userId.includes('/')) {
|
||||||
userId.includes('/')) {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
if (ReactiveCache.getCurrentUser()?.isAdmin) {
|
if (ReactiveCache.getCurrentUser()?.isAdmin) {
|
||||||
Users.update(userId, {
|
Users.update(userId, {
|
||||||
|
|
@ -1520,9 +1528,8 @@ if (Meteor.isServer) {
|
||||||
check(userId, String);
|
check(userId, String);
|
||||||
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
||||||
// Thanks to mc-marcy and xet7 !
|
// Thanks to mc-marcy and xet7 !
|
||||||
if (initials.includes('/') ||
|
if (initials.includes('/') || userId.includes('/')) {
|
||||||
userId.includes('/')) {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
if (ReactiveCache.getCurrentUser()?.isAdmin) {
|
if (ReactiveCache.getCurrentUser()?.isAdmin) {
|
||||||
Users.update(userId, {
|
Users.update(userId, {
|
||||||
|
|
@ -1538,9 +1545,8 @@ if (Meteor.isServer) {
|
||||||
check(boardId, String);
|
check(boardId, String);
|
||||||
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
||||||
// Thanks to mc-marcy and xet7 !
|
// Thanks to mc-marcy and xet7 !
|
||||||
if (username.includes('/') ||
|
if (username.includes('/') || boardId.includes('/')) {
|
||||||
boardId.includes('/')) {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
const inviter = ReactiveCache.getCurrentUser();
|
const inviter = ReactiveCache.getCurrentUser();
|
||||||
const board = ReactiveCache.getBoard(boardId);
|
const board = ReactiveCache.getBoard(boardId);
|
||||||
|
|
@ -1586,9 +1592,8 @@ if (Meteor.isServer) {
|
||||||
username = email.substring(0, posAt);
|
username = email.substring(0, posAt);
|
||||||
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
||||||
// Thanks to mc-marcy and xet7 !
|
// Thanks to mc-marcy and xet7 !
|
||||||
if (username.includes('/') ||
|
if (username.includes('/') || email.includes('/')) {
|
||||||
email.includes('/')) {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
const newUserId = Accounts.createUser({
|
const newUserId = Accounts.createUser({
|
||||||
username,
|
username,
|
||||||
|
|
@ -1618,51 +1623,52 @@ if (Meteor.isServer) {
|
||||||
subBoard.addMember(user._id);
|
subBoard.addMember(user._id);
|
||||||
user.addInvite(subBoard._id);
|
user.addInvite(subBoard._id);
|
||||||
}
|
}
|
||||||
} try {
|
}
|
||||||
const fullName =
|
try {
|
||||||
inviter.profile !== undefined &&
|
const fullName =
|
||||||
inviter.profile.fullname !== undefined
|
inviter.profile !== undefined &&
|
||||||
? inviter.profile.fullname
|
inviter.profile.fullname !== undefined
|
||||||
: '';
|
? inviter.profile.fullname
|
||||||
const userFullName =
|
: '';
|
||||||
user.profile !== undefined && user.profile.fullname !== undefined
|
const userFullName =
|
||||||
? user.profile.fullname
|
user.profile !== undefined && user.profile.fullname !== undefined
|
||||||
: '';
|
? user.profile.fullname
|
||||||
const params = {
|
: '';
|
||||||
user:
|
const params = {
|
||||||
userFullName != ''
|
user:
|
||||||
? userFullName + ' (' + user.username + ' )'
|
userFullName != ''
|
||||||
: user.username,
|
? userFullName + ' (' + user.username + ' )'
|
||||||
inviter:
|
: user.username,
|
||||||
fullName != ''
|
inviter:
|
||||||
? fullName + ' (' + inviter.username + ' )'
|
fullName != ''
|
||||||
: inviter.username,
|
? fullName + ' (' + inviter.username + ' )'
|
||||||
board: board.title,
|
: inviter.username,
|
||||||
url: board.absoluteUrl(),
|
board: board.title,
|
||||||
};
|
url: board.absoluteUrl(),
|
||||||
// Get the recipient user's language preference for the email
|
};
|
||||||
const lang = user.getLanguage();
|
// Get the recipient user's language preference for the email
|
||||||
|
const lang = user.getLanguage();
|
||||||
|
|
||||||
// Add code to send invitation with EmailLocalization
|
// Add code to send invitation with EmailLocalization
|
||||||
if (typeof EmailLocalization !== 'undefined') {
|
if (typeof EmailLocalization !== 'undefined') {
|
||||||
EmailLocalization.sendEmail({
|
EmailLocalization.sendEmail({
|
||||||
to: user.emails[0].address,
|
to: user.emails[0].address,
|
||||||
from: Accounts.emailTemplates.from,
|
from: Accounts.emailTemplates.from,
|
||||||
subject: 'email-invite-subject',
|
subject: 'email-invite-subject',
|
||||||
text: 'email-invite-text',
|
text: 'email-invite-text',
|
||||||
params: params,
|
params: params,
|
||||||
language: lang,
|
language: lang,
|
||||||
userId: user._id
|
userId: user._id,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Fallback if EmailLocalization is not available
|
// Fallback if EmailLocalization is not available
|
||||||
Email.send({
|
Email.send({
|
||||||
to: user.emails[0].address,
|
to: user.emails[0].address,
|
||||||
from: Accounts.emailTemplates.from,
|
from: Accounts.emailTemplates.from,
|
||||||
subject: TAPi18n.__('email-invite-subject', params, lang),
|
subject: TAPi18n.__('email-invite-subject', params, lang),
|
||||||
text: TAPi18n.__('email-invite-text', params, lang),
|
text: TAPi18n.__('email-invite-text', params, lang),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Meteor.Error('email-fail', e.message);
|
throw new Meteor.Error('email-fail', e.message);
|
||||||
}
|
}
|
||||||
|
|
@ -1688,7 +1694,9 @@ if (Meteor.isServer) {
|
||||||
},
|
},
|
||||||
isImpersonated(userId) {
|
isImpersonated(userId) {
|
||||||
check(userId, String);
|
check(userId, String);
|
||||||
const isImpersonated = ReactiveCache.getImpersonatedUser({ userId: userId });
|
const isImpersonated = ReactiveCache.getImpersonatedUser({
|
||||||
|
userId: userId,
|
||||||
|
});
|
||||||
return isImpersonated;
|
return isImpersonated;
|
||||||
},
|
},
|
||||||
setUsersTeamsTeamDisplayName(teamId, teamDisplayName) {
|
setUsersTeamsTeamDisplayName(teamId, teamDisplayName) {
|
||||||
|
|
@ -1760,15 +1768,12 @@ if (Meteor.isServer) {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
|
||||||
// Thanks to mc-marcy and xet7 !
|
// Thanks to mc-marcy and xet7 !
|
||||||
if (user.username.includes('/') ||
|
if (user.username.includes('/') || email.includes('/')) {
|
||||||
email.includes('/')) {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const initials = user.services.oidc.fullname
|
const initials = user.services.oidc.fullname
|
||||||
.split(/\s+/)
|
.split(/\s+/)
|
||||||
.reduce((memo, word) => {
|
.reduce((memo, word) => {
|
||||||
|
|
@ -1817,7 +1822,8 @@ if (Meteor.isServer) {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
const disableRegistration = ReactiveCache.getCurrentSetting().disableRegistration;
|
const disableRegistration =
|
||||||
|
ReactiveCache.getCurrentSetting().disableRegistration;
|
||||||
// If this is the first Authentication by the ldap and self registration disabled
|
// If this is the first Authentication by the ldap and self registration disabled
|
||||||
if (disableRegistration && options && options.ldap) {
|
if (disableRegistration && options && options.ldap) {
|
||||||
user.authenticationMethod = 'ldap';
|
user.authenticationMethod = 'ldap';
|
||||||
|
|
@ -1909,8 +1915,13 @@ if (Meteor.isServer) {
|
||||||
modifiedAt: -1,
|
modifiedAt: -1,
|
||||||
});
|
});
|
||||||
// Avatar URLs from CollectionFS to Meteor-Files, at users collection avatarUrl field:
|
// Avatar URLs from CollectionFS to Meteor-Files, at users collection avatarUrl field:
|
||||||
Users.find({ "profile.avatarUrl": { $regex: "/cfs/files/avatars/" } }).forEach(function (doc) {
|
Users.find({
|
||||||
doc.profile.avatarUrl = doc.profile.avatarUrl.replace("/cfs/files/avatars/", "/cdn/storage/avatars/");
|
'profile.avatarUrl': { $regex: '/cfs/files/avatars/' },
|
||||||
|
}).forEach(function (doc) {
|
||||||
|
doc.profile.avatarUrl = doc.profile.avatarUrl.replace(
|
||||||
|
'/cfs/files/avatars/',
|
||||||
|
'/cdn/storage/avatars/',
|
||||||
|
);
|
||||||
// Try to fix Users.save is not a fuction, by commenting it out:
|
// Try to fix Users.save is not a fuction, by commenting it out:
|
||||||
//Users.save(doc);
|
//Users.save(doc);
|
||||||
});
|
});
|
||||||
|
|
@ -2133,7 +2144,8 @@ if (Meteor.isServer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//invite user to corresponding boards
|
//invite user to corresponding boards
|
||||||
const disableRegistration = ReactiveCache.getCurrentSetting().disableRegistration;
|
const disableRegistration =
|
||||||
|
ReactiveCache.getCurrentSetting().disableRegistration;
|
||||||
// If ldap, bypass the inviation code if the self registration isn't allowed.
|
// If ldap, bypass the inviation code if the self registration isn't allowed.
|
||||||
// TODO : pay attention if ldap field in the user model change to another content ex : ldap field to connection_type
|
// TODO : pay attention if ldap field in the user model change to another content ex : ldap field to connection_type
|
||||||
if (doc.authenticationMethod !== 'ldap' && disableRegistration) {
|
if (doc.authenticationMethod !== 'ldap' && disableRegistration) {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ class TestLogin:
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
json_response = response.json()
|
json_response = response.json()
|
||||||
|
|
||||||
assert 'token' in json_response
|
assert 'token' in json_response
|
||||||
assert isinstance(json_response['token'], str)
|
assert isinstance(json_response['token'], str)
|
||||||
assert len(json_response['token']) > 0
|
assert len(json_response['token']) > 0
|
||||||
|
|
@ -43,6 +44,7 @@ class TestLogin:
|
||||||
|
|
||||||
assert response.status_code in [400, 401, 404]
|
assert response.status_code in [400, 401, 404]
|
||||||
json_response = response.json()
|
json_response = response.json()
|
||||||
|
|
||||||
assert 'error' in json_response
|
assert 'error' in json_response
|
||||||
assert json_response['error'] == 'not-found'
|
assert json_response['error'] == 'not-found'
|
||||||
assert 'reason' in json_response
|
assert 'reason' in json_response
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ class TestBoard:
|
||||||
'password': '123456'
|
'password': '123456'
|
||||||
}
|
}
|
||||||
|
|
||||||
print("🔐 Getting authentication token...")
|
|
||||||
response = requests.post(f"{base_url}/users/login", data=login_data)
|
response = requests.post(f"{base_url}/users/login", data=login_data)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
|
|
@ -22,38 +21,16 @@ class TestBoard:
|
||||||
# Store token and user info in class
|
# Store token and user info in class
|
||||||
request.cls.auth_token = json_response['token']
|
request.cls.auth_token = json_response['token']
|
||||||
request.cls.user_id = json_response.get('id', '')
|
request.cls.user_id = json_response.get('id', '')
|
||||||
print(f"✅ Token obtained: {request.cls.auth_token[:20]}...")
|
|
||||||
print(f"✅ User ID obtained: {request.cls.user_id[:20]}...")
|
|
||||||
else:
|
else:
|
||||||
request.cls.auth_token = None
|
request.cls.auth_token = None
|
||||||
print(f"❌ Login failed: {json_response}")
|
|
||||||
else:
|
else:
|
||||||
request.cls.auth_token = None
|
request.cls.auth_token = None
|
||||||
print(f"❌ Login request failed: {response.status_code}")
|
|
||||||
|
|
||||||
def test_health_check(self):
|
def test_health_check(self):
|
||||||
"""Test basic health check"""
|
"""Test basic health check"""
|
||||||
response = requests.get(f"{base_url}")
|
response = requests.get(f"{base_url}")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_get_user_boards(self):
|
|
||||||
"""Test getting information about boards of user"""
|
|
||||||
if not self.auth_token:
|
|
||||||
pytest.skip("No authentication token available")
|
|
||||||
|
|
||||||
response = requests.get(
|
|
||||||
f"{base_url}/api/users/{self.user_id}/boards",
|
|
||||||
headers={"Authorization": f"Bearer {self.auth_token}"}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
# Should return a list of boards
|
|
||||||
boards_data = response.json()
|
|
||||||
assert isinstance(boards_data, list), "Response should be a list of boards"
|
|
||||||
assert "title" in boards_data[0], "First board object should have a 'title' key"
|
|
||||||
|
|
||||||
def test_create_board_minimal(self):
|
def test_create_board_minimal(self):
|
||||||
"""Test creating a board with minimal required fields"""
|
"""Test creating a board with minimal required fields"""
|
||||||
if not self.auth_token:
|
if not self.auth_token:
|
||||||
|
|
@ -186,9 +163,6 @@ class TestBoard:
|
||||||
data=board_data
|
data=board_data
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"🚫 Unauthorized creation status: {response.status_code}")
|
|
||||||
print(f"🚫 Unauthorized response: {response.text[:200]}")
|
|
||||||
|
|
||||||
# Should require authentication
|
# Should require authentication
|
||||||
assert response.status_code in [400, 401, 403], "Should require authentication"
|
assert response.status_code in [400, 401, 403], "Should require authentication"
|
||||||
|
|
||||||
|
|
@ -202,7 +176,5 @@ class TestBoard:
|
||||||
headers={"Authorization": f"Bearer {self.auth_token}"}
|
headers={"Authorization": f"Bearer {self.auth_token}"}
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"📋 Get boards API status: {response.json()}")
|
|
||||||
|
|
||||||
# Should work with authentication
|
# Should work with authentication
|
||||||
assert response.status_code in [200, 204]
|
assert response.status_code in [200, 204]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue