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
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ RUN a2enmod rewrite headers && \
echo 'ServerTokens Prod\n\
ServerSignature Off\n\
PassEnv TOKEN\n\
PassEnv WHITELIST\n\
<VirtualHost *:80>\n\
ServerAdmin webmaster@localhost\n\
DocumentRoot /var/www/html/src\n\
Expand Down Expand Up @@ -60,4 +61,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
EXPOSE 80

# Start Apache
CMD ["apache2-foreground"]
CMD ["apache2-foreground"]
29 changes: 19 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ The longest streak is the highest number of consecutive days on which you have m

The current streak is the number of consecutive days ending with the current day on which you have made at least one contribution. If you have made a contribution today, it will be counted towards the current streak, however, if you have not made a contribution today, the streak will only count days before today so that your streak will not be zero.

> [!NOTE]
> [!NOTE]
> You may need to wait up to 24 hours for new contributions to show up ([Learn how contributions are counted](https://docs.github.com/articles/why-are-my-contributions-not-showing-up-on-my-profile))
## 📤 Deploying it on your own
Expand All @@ -164,7 +164,7 @@ The Inkscape dependency is required for PNG rendering, as well as Segoe UI font

Vercel is the recommended option for hosting the files since it is **free** and easy to set up. Watch the video below or expand the instructions to learn how to deploy to Vercel.

> [!NOTE]
> [!NOTE]
> PNG mode is not supported since Inkscape will not be installed but the default SVG mode will work.
### 📺 [Click here for a video tutorial on how to self-host on Vercel](https://www.youtube.com/watch?v=maoXtlb8t44)
Expand Down Expand Up @@ -210,14 +210,15 @@ Vercel is the recommended option for hosting the files since it is **free** and
9. Scroll to the bottom of the page and click on **"Generate token"**
10. Visit the Vercel dashboard at <https://vercel.com/dashboard> and select your project. Then, click on **"Settings"** and choose **"Environment Variables"**.
11. Add a new environment variable with the key `TOKEN` and the value as the token you generated in step 9, then save your changes
12. To apply the new environment variable, you need to redeploy the app. Run `vercel --prod` to deploy the app to production.
12. (Optional) You can also set the `WHITELIST` environment variable to restrict which GitHub usernames can be accessed through the service. Provide the usernames as a comma-separated list, for example: `user1,user2,user3`. If the variable is not set, information can be requested for any GitHub user.
13. To apply the new environment variable(s), you need to redeploy the app. Run `vercel --prod` to deploy the app to production.

![image](https://user-images.githubusercontent.com/20955511/209588756-8bf5b0cd-9aa6-41e8-909c-97bf41e525b3.png)

> ⚠️ **Note**
> ⚠️ **Note**
> To set up automatic Vercel deployments from GitHub, make sure to turn **off** "Include source files outside of the Root Directory" in the General settings and use `vercel` as the production branch in the Git settings.
> ⚠️ **Note**
> ⚠️ **Note**
> If you receive an error related to libssl or Node 20.x, you can fix this by opening your Vercel project settings and changing the Node.js version to 18.x.
>
> ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/20955511/5fb18fb5-debe-4620-9c8b-193ab442a617)
Expand All @@ -230,9 +231,9 @@ Heroku is another great option for hosting the files. All features are supported

<details>
<summary><b>Instructions for deploying to Heroku (Paid)</b></summary>

### Step-by-step instructions for deploying to Heroku

1. Sign in to **Heroku** or create a new account at <https://heroku.com>
2. Visit [this link](https://github.com/settings/tokens/new?description=GitHub%20Readme%20Streak%20Stats) to create a new Personal Access Token (no scopes required)
3. Scroll to the bottom and click **"Generate token"**
Expand All @@ -244,8 +245,9 @@ Heroku is another great option for hosting the files. All features are supported

![heroku config variables](https://user-images.githubusercontent.com/20955511/136292022-a8d9b3b5-d7d8-4a5e-a049-8d23b51ce9d7.png)

6. Click **"Deploy App"** at the end of the form
7. Once the app is deployed, you can use `<your-app-name>.herokuapp.com` in place of `streak-stats.demolab.com`
6. (Optional) You can also set the `WHITELIST` Config Var to restrict which GitHub usernames can be accessed through the service. Provide the usernames as a comma-separated list, for example: `user1,user2,user3`. If the variable is not set, information can be requested for any GitHub user.
7. Click **"Deploy App"** at the end of the form
8. Once the app is deployed, you can use `<your-app-name>.herokuapp.com` in place of `streak-stats.demolab.com`

</details>

Expand Down Expand Up @@ -285,7 +287,14 @@ Docker is a great option for self-hosting with full control over your environmen
docker run -d -p 8080:80 -e TOKEN=your_github_token_here streak-stats
```

6. Visit http://localhost:8080 to access your self-hosted instance
6. You can also optionally set the `WHITELIST` environment variable to restrict which GitHub usernames can be accessed through the service. If the `WHITELIST` variable is not set, information can be requested for any GitHub user.
Provide the usernames as a comma-separated list, for example:

```bash
docker run -d -p 8080:80 -e TOKEN=your_github_token_here -e WHITELIST=user1,user2,user3 streak-stats
```

7. Visit http://localhost:8080 to access your self-hosted instance

</details>

Expand Down
6 changes: 6 additions & 0 deletions src/stats.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

declare(strict_types=1);

require_once "whitelist.php";

/**
* Build a GraphQL query for a contribution graph
*
Expand Down Expand Up @@ -121,6 +123,10 @@ function executeContributionGraphRequests(string $user, array $years): array
*/
function getContributionGraphs(string $user, ?int $startingYear = null): array
{
if (!isWhitelisted($user)) {
throw new InvalidArgumentException("User not in whitelist.", 403);
}

// get the list of years the user has contributed and the current year's contribution graph
$currentYear = intval(date("Y"));
$responses = executeContributionGraphRequests($user, [$currentYear]);
Expand Down
13 changes: 13 additions & 0 deletions src/whitelist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

/**
* Check if a GitHub username is allowed based on the whitelist
*
* @param string $user GitHub username to check
* @return bool True if the username is in the whitelist or if the whitelist is empty, false otherwise
*/
function isWhitelisted(string $user): bool
{
$whitelist = array_map("trim", array_filter(explode(",", $_SERVER["WHITELIST"] ?? "")));
return empty($whitelist) || in_array($user, $whitelist, true);
}
30 changes: 30 additions & 0 deletions tests/StatsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,36 @@ public function testInvalidUsername(): void
getContributionGraphs("help");
}

/**
* Test that an valid username can be accessed with whitelist
*/
public function testValidUsernameWithWhitelist(): void
{
$_SERVER["WHITELIST"] = "DenverCoder1";
try {
$contributionGraphs = getContributionGraphs("DenverCoder1");
$this->assertIsArray($contributionGraphs);
$this->assertNotEmpty($contributionGraphs);
} finally {
unset($_SERVER["WHITELIST"]);
}
}

/**
* Test that an not whitelisted username returns 'not whitelisted' error
*/
public function testNotWhitelistedUsername(): void
{
$_SERVER["WHITELIST"] = "DenverCoder1";
try {
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage("User not in whitelist.");
getContributionGraphs("help");
} finally {
unset($_SERVER["WHITELIST"]);
}
}

/**
* Test that an organization name returns 'not a user' error
*/
Expand Down