Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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, 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